Professional Documents
Culture Documents
Blaise 18uk PDF
Blaise 18uk PDF
Pascal
18
irr
or
,m
irr
or
on
th
wa
ll,
wh
at
September 2011
Publisher: Foundation for Supporting the Pascal Programming Language
in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
Stichting Ondersteuning Programmeertaal Pascal
BLAISE
PASCAL MAGAZINE 18
ALL ABOUT DELPHI AND DELPHI PRISM(.Net), LAZARUS & PASCAL AND RELATED LANGUAGES
CONTENTS
Articles
Editor - in - chief
Book review
James Duff page 4
Writing New Components in Lazarus, Part 2
Howard Page-Clark page 6
Delphi XE2 is the future - theme XE2
Fikret Hasovic page 13
Cylindrical anamorphosis
Peter Bijlsma page 17
Web Service Toolkit as a rich type framework
for Plug-in development
Inoussa Ouedraogo page 21
Polygon colouring and area calculation
David Dirkse page 24
Creating a Database program from scratch - part 1
Detlef Overbeek page 29
An Android client for a DataSnap Server Part 2
Daniele Teti page 34
Anti-freeze for the VCL
Alexander Alexeev page 39
Delphi XE2 New Features - theme XE2
Bob Swart page 46
Learning to use FastReport 4 for VCL - Part 3
Sergey Lyubeznyy page 53
Question and Answers
Henk Schreij page 16
Creating 64-bit applications with Delphi XE2 - theme XE2
Jeremy North page 64
kbmSQL Structured Query Language
for your memory table
Kim Madsen page 72
High Level Multithreading
PrimozGabrijelcic page 76
TMS Scripter Studio Pro
Wagner R. Landgraf & Bruno Fierens page 81
FreePascal vectorial
Felipe Monteiro de Cavalho page 90
The tale of the Nigthtingale
Detlef Overbeek page 96
Using the Advantage Database Client Engine
Michal Van Canneyt page 101
Configuring OLE DB Providers and ODBC Drivers
Cary Jensen page 107
Delphi XE2 LiveBinding - theme XE2
Bob Swart page 113
Advertisers
Alexander Alexeev
Peter Bijlsma,
Michal Van Canneyt, Marco Cant,
David Dirkse, Daniele Teti
Bruno Fierens
Primo Gabrijeli,
Fikret Hasovic
Cary Jensen
Wagner R. Landgraf, Sergey Lyubeznyy
KIm Madsen, Felipe Monteiro de Cavalho
Jeremy North,
Tim Opsteeg, Inoussa Ouedraogo
Howard Page-Clark, Herman Peeren,
Michael Rozlog,
Henk Schreij, Rik Smit, Bob Swart,
Editors
Peter Bijlsma, Rob van den Bogert, W. (Wim) van
Ingen Schenau,
Correctors
Howard Page-Clark, James D. Duff
Copyright Page 118
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 ( 2011 prices )
1: Printed version: subscription 60.-(including code, programs and printed magazine,
4 issues per year including postage).
2: Non printed subscription 35.-(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.
office@blaisepascal.eu
COMPONENTS
DEVELOPERS
Subscription department
Edelstenenbaan 21 / 3402 XA IJsselstein,
The Netherlands / Tel.: + 31 (0) 30 890.66.44 /
Mobile: + 31 (0) 6 21.23.62.68
5: Target Platforms
Understanding the differences among the platforms
Lazarus supports - Windows 32 and 64, Windows CE,
Unix Systems (FreeBSD) and MacOS X
6: The Class Libraries
The class libraries available: RTL, FCL and LCL, and a
description of the functionality supplied with Lazarus in
its two hundred components.
7: Porting Delphi Components
How to port existing Delphi components to Lazarus
(where possible)
COMPONENTS
DEVELOPERS
Book Review
LAZARUS The Complete Guide (continuation)
USB STICK
COMPONENTS
DEVELOPERS
expert
Lazarus 9.30
COMPONENTS
DEVELOPERS
TGraphicControl
TWinControl
TCustomControl
TCustomxxxx
TGraphicControl (or a TGraphicControl descendant)
is the best ancestor for visual components that do not require
user interaction, but just provide information to the UI;
components such as labels, shapes, and progress indicators.
On the other hand, components that must respond to mouseclicks or gestures or keystrokes (components that will receive and lose
focus) will require a window handle (or its equivalent in non-Windows
operating systems) and must descend from TWinControl or
one of its many descendants. Often the best choice in either
case is a TWinControl or TGraphicControl descendant
named TCustomXXX where XXX might be Label, Edit,
StringGrid and so on. Nearly all the standard LCL controls such
as TLabel, TEdit, TStringGrid and so on have an
immediate ancestor named TCustomLabel,
TCustomEdit,or TCustomStringGrid.The immediate
TCustom ancestor has all the designed functionality of its
descendant, but has few, if any, published properties or events.
This is a deliberate LCL (and VCL) architectural feature, which
keeps controls as small and sparing of memory footprint as
possible. The actual, everyday control such as TLabel is
nothing more than a TCustomLabel with suitable properties
and events published for availability in the Object Inspector.
Our new TCompanyLabel component is just a customised
TLabel. In this particular case there is little to be gained by
descending from TCustomLabel, so we will descend directly
from TLabel, thereby accepting all TLabel's published
properties and events. It is often the case that a new component
does not need all the published properties of its ancestor. Here
there is a distinct advantage in descending from a TCustom
class, since unneeded publishing of properties and/or events
can thereby be avoided.
Component Identity
To identify the new component we need a name for it, and a
unique icon (if it is to be distinguishable from nearly 200 other
components on the Component Palette). Both name (text) and icon
(picture) deserve careful consideration, and of the two, the name
is more important because you must give a component a name,
and it must be unique. If you do not provide an icon for a
registered component Lazarus will supply a default icon for it in
the Component Palette (see Figure 1). Provided there are no other
icon-less components on that Palette page, this will suffice.
However, if you have several such components on a single
Palette page, the icons will indicate how many such components
there are, but obviously won't identify which is which.
Users who have enabled the Hints for component palette
checkbox in the Hints section of the Environment
Desktop page of the IDE Options dialog will see the
component's name in a hint box if they move the mouse over
the icon. Users who don't have this checkbox enabled will be
shooting in the dark. (You reach the IDE Options dialog via the
ToolsOptions menu).
COMPONENTS
DEVELOPERS
COMPONENTS
DEVELOPERS
{$I Companylbl_icon.lrs}
RegisterComponents('Additional',[TCompanyLabel]);
end;
end.
You'll see that this is just a standard Object Pascal unit, including
compiler directives for objfpc mode (rather than delphi mode) and
for treating the keyword string as meaning ansistring ({H+}).
Eight units which you are likely to need are included in the uses
clause, the new component class declaration is written out as a
skeleton descending from the ancestor you specified earlier,
and a Register prodecure is declared and implemented for you.
Notice that this two-line Register implementation associates the
icon you specified with the new component via a {$I }
Figure 7: The information needed to complete the New Component page directive which has inserted a Lazarus-generated resource file
(based on the icon you specified) named Companylbl_icon.lrs,
When specifying the Unit File Name: information there is an
and places the registered component on the Palette page given
intruigingly labelled [ <> ]button beside the ellipsis [ ]button earlier via the RegisterComponents()procedure.
(that brings up a Save As dialog). The [<>] button is a Shorten or
Of course you could write all this code yourself, but it is easier
expand button which usefully shows either the short filename
and less error-prone to use the New Component page of the
(source\companylbl.pp here) or the expanded filename
Package Editor to do it for you. If we look at the Package Editor
with the full path. This affects the name display only.
now (via the Window Package CompanyLabel* menu) we see
Lazarus saves all files with fully qualified paths, however much
that Lazarus has now added the LCL in the Required Packages
of that path is actually displayed in the dialog. Clicking the
treeview node (TLabel obviously needs the LCL) and the
[OK] button creates a new companylbl.pp in the location
component file \source\companylbl.pp as a package
you have specified. If the package has not registered any of the
file, as we would expec (see Figure 9).
paths you've just entered an intermediate dialog will pop up
If we highlight the companylbl.pp file you'll see in the File
asking if you want the package to add the paths (to which you
Properties section near the bottom of the Package Editor
should agree).
window that two new checkboxes have appeared (Register
unit and Use unit) which are both now (correctly) ticked
(Figure 9). The checked Register unit checkbox indicates that
companylbl.pp contains a Register procedure in its interface
which will be called by the IDE to place the component on the
Component Palette. The checked Use unit checkbox indicates
that this unit will be compiled automatically when the package is
compiled, which is what we want to happen. The asterisk at the
end of CompanyLabel* in the Package Editor's title indicates
that the new package has not yet been saved.
Figure 8: A confirmation dialog for changing the unitpath
COMPONENTS
DEVELOPERS
TCompanyLabel = class(TLabel)
private
procedure SetDefaults;
public
constructor Create(TheOwner: TComponent);
override;
end;
The new constructor and SetDefaults procedure are as follows:
Constructor TCompanyLabel.Create(TheOwner:
TComponent);
Begin
Inherited Create(TheOwner);
SetDefaults;
End;
Procedure TCompanyLabel.SetDefaults;
Begin
:= 'Timeless Limited';
Caption
Autosize := True;
Alignment := taCenter;
:= clMoneyGreen;
Color
ParentColor := False;
:= clMaroon;
Font.Color
:= False;
ParentFont
:= 'Calibri';
Font.Name
:= [fsBold, fsItalic];
Font.Style
Font.Height := -19;
:= 2;
BorderSpacing.InnerBorder
:= 1;
BorderSpacing.Around
:= 23;
Constraints.MinHeight
:= 140;
Constraints.MinWidth
Hint:='Timeless can meet all your software needs';
:= True;
ShowHint
Transparent := False;
End;
10
COMPONENTS
DEVELOPERS
Figure 13: The newly dropped component with the 'wrong' caption
What is going on here? Well it turns out that after construction
Lazarus does some housekeeping, which includes setting the
default name of new components if the component's
ControlStyle property contains the csSetCaption
flag (see the previous article for more detail). The Caption is set to
match the name. Since all new components have the
csSetCaption flag set by default, we have to override this
Lazarus behaviour to get our constructor's Caption-setting code
to persist. So we must add a line to the SetDefaults procedure
given earlier as follows:
Procedure TCompanyLabel.SetDefaults;
Begin
ControlStyle := ControlStyle - [csSetCaption];
Caption := 'Timeless Limited';
// etc.
End;
Figure 15: The Package Editor showing the newly registered plugin
Moreover, if we click the [Use >>] toolbutton when the
component package is open in the Editor we now see an
additional Uninstall menu option available.
Lazarus recognises that not all registered plugins are there to stay.
If we change our mind about TCompanyLabel, we now have
the option to uninstall it. After putting up a confirmation dialog,
Lazarus lets us uninstall the component.
Figure 14:
TCompanyLabel at
design time and runtime
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
COMPONENTS
DEVELOPERS
11
The IDE Integration page has radiobutton options set for you
by Lazarus at installation, but the preferences can be changed if
for some reason you want to override Lazarus' guess as to
whether the component is runtime only or runtime and design
time (Figure 19).
If you have FPDoc documentation files (which for a component of
any complexity you ought to take the trouble to provide, so users have local
access to important details of usage and behaviour) you specify their
location here too.
The author
Howard Page-Clark lives near Poole, Dorset in the
UK. He is a hobby programmer who first made use
of Delphi to develop database programs when
working as a book keeper for a disability charity.
After a period as a teacher of science to teenagers,
he now works as a volunteer with charities and
church groups. He is married to Jodi, and they have
four adopted children, now all grown-up.
COMPONENTS
DEVELOPERS
expert
DELPHI XE2
COMPONENTS
DEVELOPERS
13
Figure 3
The name of the active platform is boldfaced in the Project
Manager. The native Win32 platform is the default active
platform.
To make the current project a crossplatform project
Add a target platform (right-click the Target Platforms node,
and click Add Platform):
uses
System.SysUtils, System.Types, System.UITypes,
System.Classes, System.Variants, FMX.Types,
FMX.Controls, FMX.Forms, FMX.Dialogs;
Figure 4
On the Select Platform dialog box, select the platform you want to add,
and click OK:
Figure 5
14
COMPONENTS
DEVELOPERS
Figure 7
DataSnap Mobile Connectors
in RAD Studio XE2
COMPONENTS
DEVELOPERS
15
www.blaisepascal.eu
16
COMPONENTS
DEVELOPERS
expert
COMPONENTS
DEVELOPERS
Pvhi := PvMax;
Pvlo := 0;
Mid := 180;
Temp := (Pamax-PaMin)*BMRatio/(HVRatio*2);
Pahi := Mid + Temp;
Palo := Mid - Temp;
end;
end;
COMPONENTS
DEVELOPERS
19
20
COMPONENTS
DEVELOPERS
So now you can surprise your guests by showing your homemade anamorphic pictures, provided you have a shiny cylinder
(or lipstick). If you want to improve the program, feel free to do
so and let me know. Maybe you can speed up the processing by
using the SinCos function in the math unit (which
works only with variables of type extended).
I also didn't try whether the Canvas.Pixels property is
faster or slower than the Scanline property.
So download the source code and try it out!
It's written in Delphi 7, and therefore usable by the many
hobbyists among us.
There's only one thing that annoys me:
sometimes I get an Access violation in the try except
loop. I couldn't discover the cause, but as the program appears
to continue without problems, I just ignore the error. Any
suggestions for a solution?
See you next time.
peter @blaisepascal.eu
expert
Lazarus
COMPONENTS
DEVELOPERS
21
Figure 3: Architecture
22
COMPONENTS
DEVELOPERS
SUMMER OFFER:
LAZARUS
Notes :
[1]
http://wiki.lazarus.freepascal.org/Web_Service
_Toolkit,
[2]
Lazarus The Complete Guide,
ISBN 978-94-90968-02-1, Blaise Pascal Magazine,
COMPONENTS
DEVELOPERS
Pag 87
http://www.blaisepascal.eu/index.php?actie=./s
ubscribers/lazarusbookinfoEnglish
COMPONENTS
DEVELOPERS
23
expert
Introduction
This articles describes both how to colour a polygon
and how to calculate its area. These two tasks are
actually related since both require that you first divide
the polygon into its constituent triangles. This
triangulation is the hardest part of the task.
Figure 2
In the case of two intersecting vectors (red and blue) there is a point
S where they meet. Since S is on both the red and the blue vector,
we may calculate the f values (f1 for red, f2 for blue) for S. f1 and f2
then give an indication of the relative positions of the vectors.
Figure 3
Figure 4
In case (1) B is an internal angle of less than 180 degrees (an
'inside' angle) because the forward extension of AB does not
intersect other polygon vectors.
In case (2) the forward extension of AB intersects DE at point
S, so B forms an internal angle greater than 180 degrees (an
'outside' angle).
In case (1) we may colour triangle ABC. In case (2) with the
'outside' angle, we must not colour triangle ABC, since it is
external to the polygon.
However look at the following figure (5):
A line (edge) has a starting and ending point. Such a line is called
a vector, because more than one number is required to describe
it (two numbers are required in 2D geometry).
The figure below shows line l with vector AB.
An arbitrary point on line l can be characterized by a single
number, a factor which I call f.
Point A, the starting point, corresponds to f = 0.
Point B, the ending point, corresponds to f = 1.
To the right of B (the forward extension of AB)
are points that correspond to f > 1
To the left of A (the backward extension of AB) are points that
correspond to f < 0.
(You can refer to Appendix 1 at the end of the article for a fuller
mathematical description.)
24
COMPONENTS
DEVELOPERS
Figure 5
Diagram (1) shows polygon ABCD. At point B there is an
internal angle of less than 180 degrees (apparently an 'inside'
angle), but triangle ABC may not be coloured because there is a
polygon vertex (D) inside triangle ABC. Diagram (2) shows how
to recognize this situation. Construct vectors CA and BD and
investigate if the forward extension of BD intersects CA.
Figure 6
The extended vector AB intersects FG at S1 (and HI at S2)
so angle B (on the basis of the simple arrow-case algorithm above) is a
forbidden 'outside' angle. However, B is less than 180 degrees
and triangle ABC is internal to the polygon.
So, the 'outside' angle test needs refining.
We notice that the extension of AB intersects two other polygon
vectors, not one. That is the important clue towards a correct
solution. If there are an odd number of intersections then we
have an 'outside' angle. If there are an even number of
intersections then we have an 'inside' angle. But there still is a
problem to be solved, which can be seen in the following figure:
Figure 9
The sum for all AB crosscounts is 1 -1 + 2 = 2.
This is not equal to zero so B is not an 'inside' angle.
All that remains is to design a method that will recognize a
positive or negative crosscount.
Clearly we should measure the angle between the vectors to do
this. We shift the vectors parallel-wise until their starting points
coincide.
We notice
RIGHT...0 < angle < 180 degrees ...or
-360 < angle < -180 degrees (relative to AB)
Figure 7
The extension of AB intersects two other vectors (CD and DE)
exactly at their starting and ending point D.
In case(1) B is an 'outside' angle, but
in case (2) B is an 'intside' angle.
In case (1) we should colour triangle ABC whereas
in case (2) we should not colour ABC.
What then should we do? I present the following solution,
having evaluated all possibilities:
Figure 10
The angle between two vectors is the difference of their
directions.
Rather than using degrees (0..360) ,we calculate in radians
(0..2*pi), where pi = 3.14,
the ratio of a circle's circumference to its diameter.
The directions are shown in the following diagram, in which
clockwise movement is positive:
Figure 8
Position yourself at point A and look beyond B.
Vectors intersect the extension of AB and they may cross to the
right considered positive crossings (CD, EF, GH) or to the
left considered negative crossings (IJ, KL, MN).
Figure 11
COMPONENTS
DEVELOPERS
25
area = sqrt(s*(s-a)*(s-b)*(s-c)).
The first and last points in the array must be the same so
that the polygon is "closed".
Using the points array, a vectorlist is generated.
type Tvector =
record
x,y : longInt; // the coordinates of starting point
dx,dy : longInt; // the horizontal, vertical lengths
dir : double; // the direction in radians
end;
(x,y) of A,B,C
This function returns the area and draws the polygon on the
previously selected canvas.
procedure
procedure
procedure
procedure
color
procedure geoClear;
//triangles on/off
procedure setShowTriangles(trMode : boolean);
//message of code
polyresultcode : byte;
Figure 13
In the figure above vector 1 gets replaced by the sum (AC)
of vectors 1 and 2, and points A,B,C are entered in the
trianglelist.
26
COMPONENTS
DEVELOPERS
points
buildtrianglelist;
if polyresultcode <> 0 then exit;
procedure checkpoints;
// draws point p
COMPONENTS
DEVELOPERS
27
Appendix 1
()( ) ( )
()( ) ( )
( ) ( )
(
)
dx1
dx2
The notation of vector AB= dy1 BC= dy2
(see figure below)
dx1+dx2
The sum of two vectors AB+BC = dy1+dy2
x1 + f1*dx1 = x2 + f2*dx2
y1 + f1*dy1 = y2 + f2*dy2
( )( f.dx1
f.dy1)the vector is multiplied by f.
let
d = dx1.dy2 - dx2.dy1..and
vx = x2 - x1 ...and
vy = y2 - y1 ...then
f1=
vx.dy1-vy.dx1
vx.dy2-vy.dx2 and similar
f2=
d
d
intersection point.
Appendix 2
()
()
and
()
( )( ) ( )
Note:
f=0 for point A, the starting point of the vector.
f=1 for B, the end of the vector.
For 0 < f < 1 a point is located between A and B.
If f > 1 the point is situated on the forward extension of AB.
If f < 0 then a point is on the backward extension
(left of A in this case) of AB.
28
COMPONENTS
DEVELOPERS
David Dirkse
Born 1945 in Amsterdam, David joined Control Data
Corporation in 1968, after studying electrical engineering.
As a hardware engineer, he was responsible for the
installation and maintenance of mainframes at scientific
data centers in the Netherlands.
With the decline of CDC around 1990, he studied
mathematics and became a math teacher.
His hobbies are programming, in particular educational
software, math algorithms, and puzzle solving.
http://home.hccnet.nl/david.dirkse
expert
by Detlef. D. Overbeek
1. What database?
The database handling tool must be capable of creating
the Firebird database.
2. Auto creation and visual design
It should be able to automatically create tables and allow
you to design them visually.
3. Reengineering
We need reengineering since we already have
many existing tables.
4. The ability to create and then visually
alter the design
We need a tool for SQL scripting that works in two
directions: create and design eventually in visual
alterations (the ones that show in your design overview).
5. Design updates change the underlying tables,
and vice versa
The first hurdle: the Database
6. Support printable overviews
We already have an application in rudimentary form which
Support printable overviews, at least A3 or bigger,
works after a fashion. However, it is not adequate for our
in colour.
current and future needs.
I think it's best to start from scratch, using only some ideas and 7. Create images in a variety of popular formats
Be able to create images with several different
details from our current application. So we have reusable parts
and ideas, but we have not yet settled on a database. Since we are
sorts of extensions.
a user group, we need to use open source where possible to keep 8. Have export and import capabilities to
the price down, which limits our choices. A further issue is
other databases
choice of the operating system on which the database server will 9. Have a data pump
run.
10. Documentation
Will it be Linux, Windows, Mac or Android? How can all our
Last but not least: have very good quality
users have easy access? Linux is cheap (almost free), but then you
documentation.
will need to use an extra helper language for creating a lot of
internet-network functions in something like PHP. Since all the
user group are familiar with Pascal, I suppose it would be best to And now the million dollar question:
Can we as a poor user group get that free? Who might help us?
keep as close as possible to Pascal.
We are of course poor, but not short of fantasy. Why not ask
So the next question is whether we use Delphi or Lazarus
the
manufacturers? And within no time we came up with the
libraries? It doesn't really affect the choice of database, especially
solution. There is a Dutch company that offers such a product,
if we opt for the Interbase equivalent : Firebird. But...
and as chauvinistic as I am - I love that.
If we use some of the best component suites for database
connection (from Components4Developers) as well as other So we gave it a try and they responded positively: we could have
their tool at no cost. (I must admit that they're one of our advertisers:
attractive offerings from TMS Software we should opt for
Upscene). They have created a program called Database
Delphi. (TMS surprises us time and again. At the recent Delphi
Conference in Cologne (Kln), Germany they presented an astonishing new Workbench which meets all of our needs. So let's start testing
product: Desktop - Touch Table. I was excited - like a little boy who had it
just found the ultimate toy when I realised the potential impact it might
have).
Since the TMS Suite is not yet fully available for Lazarus (they
are still working on it) I opted for the Delphi flavour of Pascal,
where TMS provides a full set of reliable components, and
To receive a printed copy of the magazine
Firebird will be our database.
you need to go to our website to place your order
29
Figure 3:
Firebird's local SQL dialect
30
COMPONENTS
DEVELOPERS
COMPONENTS
DEVELOPERS
31
Figure 7:
You can create and edit annotations.
COMPONENTS
DEVELOPERS
COMPONENTS
DEVELOPERS
33
expert
(http://developer.android.com/sdk/installing.html)
@Override
protected void onResume() {
super.onResume();
refresh_data();
}
private void refresh_data() {
todo_list = new RESTProxy(this).getToDos();
lv.setAdapter(new ToDoListAdapter
(this, todo_list));
}
@Override
public void onItemClick(AdapterView<?> parent,
View view, int position, long id) {
Intent i = new Intent(this, EditToDoActivity.class);
http://developer.android.com/guide/topics/fundamenta
ls.html#appcomp
Layout
The commonest way to define the appearance of an activity is
using an XML layout file. The structure of the XML used by
Android for layout it is a reminiscent of an HTML web page.
Each element within the XML can be either a View or a
ViewGroup (or one of their descendants). The name of the
XML elements of the layout file correspond to the JAVA class
that represents them. So a <Button/> element creates an
instance of the Button within the GUI and a
<LinearLayout/> element creates an instance of the
LinearLayout ViewGroup. When loading resources,
Android initialises the objects defined in the XML file layout,
creating concrete functional objects. So you can imagine the
layout xml file as a text representation of a complex object tree.
For a Delphi or Lazarus programmer, what comes the closest in
concept to an Android layout file is the DFM or the LFM file.
View and Event Handling
The activities are your application's screens. An activity is the
Android counterpart to the Delphi/Lazarus Form. So,
whatever the user can see, is necessarily contained by an
Activity. Every visible element within Android is called a View
and is drawn on an activity.
34
COMPONENTS
DEVELOPERS
Studying the code, you will learn how to connect a ToDo list
retrieved from the server to a ListView, how to open a
secondary activity, how to create ing menus for the activity, how
to manage user preferences, and how to use Toast. It is not the
aim of this article to be an Android tutorial, but just
understanding these simple mechanisms you will be able to
develop a simple Android application that does something
really functional.
Let's having take a look at the most significant proxy methods,
starting with in the method that returns the complete ToDo list:
COMPONENTS
DEVELOPERS
35
Daniele Teti
Figure 2: The code to get the ToDo list and to convert the JSON array ToDo items into an ArrayList
As can be seen in Figure 2, The ToDo list is returned from the service as an array of JSON objects. I have written a method that
takes care of converting the JSON representation of a ToDo into a real List<ToDo>. At this point the code shown should be
clear. Creating and editing a ToDo item it is just a little bit more complex, because we have to handle the body of the request.
COMPONENTS
DEVELOPERS
37
Please notice at line 56 of Figure 3 the URL is built, to identify which ToDo item to edit, using parameters. The same criterion is
used by the deleteToDo method.
When you have to work on a single ToDo, another activity is launched. The code to start another activity, with or without extra
Intent data, is shown in the Figure 4:
Summary
In this quick introduction to the Android world, we have only scratched the surface of
the subject. We but have nevertheless been introduced to several key concepts that
allow readers to start developing DataSnap REST services in Delphi. You can also take
full advantage of mobile device features in allowing an Android application to manage
remote data. You can find the complete application in the sample folder.
If you would like to learn more about it, I would recommend you read a good text on
Android, and above all, I recommend that you study the samples in the SDK provided
by Google. It They can be found under the directory samples \<your-platformnumber> of the SDK .
Figure 5.
38
COMPONENTS
DEVELOPERS
expert
All these things underline to the user that your application has
hung even if this might be not true. Your application may just
be busy with processor-intensive work and will revert to full
responsiveness after some delay.
A specific case of this situation is the question (often asked by
newbies): when you do time-consuming work, how can you
implement an immediately effective Cancel button?
The window is not responding so how can a user click on the
button?
OK, let's start with the basic question Why do these things
happen?
It is because Windows doesn't know what you want to show in
your windows. It doesn't know how you want to react to a
button-click.
That's why Windows tells you Please, redraw your windows,
and The user just clicked on your button please, do
something.
Typically, this conversation goes on under the hood of the VCL
library and you deal only with the high level end result
(for example, a Button1Click OnClick event handler).
If your application is busily doing some work it can't answer
these Windows requests, which leads to the appearance of a
hung application.
Threads?
This happens because code running in a single thread can only
do one thing at a time. Either it is working or it is responding The thing is that processes (applications) don't run any code at
all. They are just containers for threads.
to Windows' requests, but not both simultaneously.
COMPONENTS
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
39
DEVELOPERS
COMPONENTS
DEVELOPERS
Incidentally, notice how we're avoiding the magic button antipattern discussed in the previous issue of Blaise Pascal
Magazine. The EnumFiles routine is fully isolated and can easily
be moved somewhere else to allow external calls or even
simultaneous multi-threading.
Run this code, enter any folder with a huge number of files into
Edit1 (say, C:\) and click on Button1.
You'll see that the application hangs for some minutes, while it
searches for files. Experiment by minimizing and restoring the
application, dragging it over the desktop, or clicking on controls
inside the form. You'll see how the application fails to react
properly.
uses
TasksEx;
Folder: String;
begin
Button1.Enabled := False;
try//1
Str := TStringList.Create;
try//2
Folder :=
IncludeTrailingPathDelimiter(Edit1.Text);
EnterWorkerThread;
try;//3
EnumFiles(Folder, Str);
finally;//3
LeaveWorkerThread;
end;//3
Memo1.Lines.BeginUpdate;
try//4
Memo1.Lines.Assign(Str);
finally//4
Memo1.Lines.EndUpdate;
end;//4
finally //2
FreeAndNil(Str);
end;//2
finally //1
Button1.Enabled := True;
end;//1
end;
Okay, so the main searching code was left unchanged, but we've
wrapped it between calls to EnterWorkerThread and
LeaveWorkerThread. Run the application now, and notice how
beautifully it behaves itself: the application doesn't hang, it
allows you to drag it around, minimize, or restore it. It even
accepts clicks on controls inside its window! (That's why we've
locked Button1 for the search duration to prevent the user running the
search again, while first search is still running). All this happens
because code between EnterWorkerThread and
LeaveWorkerThread (i.e. the call to EnumFiles) is now running
in a separate background thread, so main thread is free to do
message pumping and UI processing.
Rules
Even though this was a very simple example, it illustrates several
important rules:
1. Calling EnterWorkerThread/LeaveWorkerThread must
always to be like this:
EnterWorkerThread;
try // do work
finally
LeaveWorkerThread;
end;
41
5. You must not access the VCL from any secondary thread.
In our first example we passed Memo1.Lines to EnumFiles, so
the AFiles.Add(S) line was adding a string directly to Memo1
therefore working with the VCL(since a memo is a VCL control).
Because this is not allowed we must get rid of this code.
42
COMPONENTS
DEVELOPERS
Run the application and start a long search. Pay attention to the
Stop button and how its state changes during the search.
Now, click it while the search is still running the search will be
halted.
How does this work?
The first and most important thing is that you must call the
CheckAbort routine in your secondary thread from time to time
this is a mandatory condition.
That's because there is no clean way to stop the thread from
outside. You can, of course, do it in a dirty way (a forced
Terminate), but doing so will leave a lot of trash in the process.
The correct way to stop the thread's work is to let the thread
itself do so. You let the thread check for a cancel condition and
stop work, if it's appropriate. That's exactly what the
CheckAbort routine does. If there is no cancel command it just
exits without doing anything. If there is a cancel command then
the routine will raise an EAbort exception.
Raising this exception will stop current work in the thread and
pass the exception up to the main thread, where it will be
processed by the Application.HandleException method.
This (by default) just ignores EAbort.
So where is the command to exit issued?
That's done by calling AbortAllWorkerThreads. As you will
have guessed this method stops all running secondary tasks.
Other issues and technical details
A secondary (worker) thread is picked randomly from the system
thread pool (by default via the QueueUserWorkItem function).
If there are no free threads then code execution will be delayed
(queued), until one of the worker threads completes and comes
free. The number of threads in the pool is controlled by passing
flags to the QueueUserWorkItem function (see the description of the
function in MSDN). By default the limit is 512 threads.
While secondary threads run your code, the main thread cycles
the message pump constantly by calling
Application.HandleMessage. The cycle ends with the end of all
worker threads started via EnterWorkerThread. During this cycle
code may be invoked which calls
EnterWorkerThread / LeaveWorkerThread again (for
example, when the user has clicked a button).
Therefore at any moment, there might be multiple code blocks
running (code blocks between EnterWorkerThread and
LeaveWorkerThread), including the case of multiple instances
of the same code running. That's why you may need to explicitly
prevent such code starting again (as we've done in our examples by
disabling the button that starts that code sequence running). Such calls are
termed parallel calls.
The first call to LeaveWorkerThread will continue to run only
after all parallel calls have been completed. In other words the
very first call to EnterWorkerThread will wait until all later
calls to EnterWorkerThread have been completed.
If all such calls have done their work before the first call does its
work there will be no waiting at all. This is the same as in a
single-threaded application, when you call
Application.ProcessMessages and the message invokes
another time-consuming message handler, so
Application.ProcessMessages will not return control to
you (the caller) until that handler has done its work.
This case should not be confused with nested calls to
EnterWorkerThread:
COMPONENTS
DEVELOPERS
43
try
...
finally
LeaveWorkerThread;
end;
end;
There may be another article about the TasksEx unit and its
additional features in a future issue of Blaise Pascal Magazine.
finally
LeaveWorkerThread;
end;
To receive a copy of
the printed magazine
you need to
order a subscription
from our website:
www.blaisepascal.eu
COMPONENTS
DEVELOPERS
COMPONENTS
DEVELOPERS
Pag 45
expert
By Bob Swart
DELPHI XE2
This can be used on just about any VCL application (just about,
since some code cannot be migrated to 64-bit the BDE, for example, is a
collection of 32-bit DLLs that will not ever (never!) be migrated to 64-bit
yet another good reason to move away from the BDE today rather than
tomorrow).
Apart from 32- and 64-bit Windows, Delphi XE2 also offers
native Mac OS X as a target platform. The IDE remains a 32-bit
application, however, so running (launching) a 64-bit Windows or
a Mac OS X application from the IDE requires some
tinkering
XE2 Editions
Project Targets
Speaking of Editions, there are now no less than five (5!)
Where previous Delphi projects had a special node in the
different Delphi XE2 editions available to buy. These are listed
Project Manager for the Build Configuration, Delphi XE2
below with pricing details both for first-time buyers and for
upgrading. All prices are shown without VAT, and these editions introduces the Target Platform node. This can have a value of
32-bit Windows, 64-bit Windows, and OS X (sometimes seen as
are available from http://www.bobswart.com for EU
OSX32,
so most likely 32-bit). Since the Delphi XE2 IDE itself
customers, together with a corresponding 12 month
is
still
a
32-bit
Windows application, by default a new project
subscription price.
will be created for the 32-bit Windows target. Depending on the
Delphi XE2
New User Upgrade
Subscription
project type, we can add one or more Target Platforms.
Starter
199
149
n/a
A VCL project, for example, can be targeted for both 32-bit and
64-bit Windows, but not Mac OS X. The VCL is really tied to
Professional
899
499
270
the Windows API, and is just about impossible to migrate to
Enterprise
1999
1299
600
another platform. If you want to create an application for Mac
Ultimate
2999
1999
900
OS X, you have to use either a console application (which can be
Architect
3499
2299
1050
handy at times, but may not be exactly what you have in mind when you
think of a Mac application), or you have to create an application
You can upgrade from Delphi 2007 or later. However, after
using a special cross-platform application framework called
December 31st 2011, you will no longer be able to upgrade from FireMonkey. As you can read in Fikret Hasovic's article
Delphi 2007, and the only eligible versions to upgrade from will elsewhere in this issue of Blaise Pascal Magazine, it's easy to add
be Delphi 2009, 2010 or XE. Delphi XE2 Ultimate is the same
a 64-bit VCL target to a project. Make sure you read Jeremy
as Delphi XE2 Enterprise with the addition of DB
North's detailed coverage of creating 64-bit applications with
PowerStudio. Delphi XE2 Architect is the same as Delphi XE2 Delphi XE2 as well. But for Mac OS X we have to use a
Enterprise with the addition of ER/Studio. These two database different framework, called FireMonkey.
products are both from Embarcadero (now the proud owner of
Delphi). To be honest, as a reseller I mostly sell Professional and FireMonkey
Enterprise editions of Delphi. The Starter edition is rather
In order for an application to look like a Windows application
limited, so check out the conditions and constraints before on Windows, and a Mac application on Mac OS X,
you purchase a Starter edition (especially if you plan to make some Embarcadero had to bring in (read: buy) a whole new set of
money with it). Apart from these five commercial editions, you can visual components a new GUI framework. They actually call it
also get special Academic versions of these editions, but only if the FireMonkey Application Platform, and it is specifically
you are a registered student at a school or university (obviously,
designed for cross-platform application development. Note that
these Academic editions cannot be used for any commercial development).
the VCL is not dead or being replaced. The VCL is still the best
Finally, you can download a 30-day trial edition of Delphi XE2 solution for native Windows applications (32-bit and/or 64-bit).
Architect to get a hands-on feeling of what the product is
But for cross-platform applications that need to move to other
capable of.
platforms like Mac OS X (and in the future probably Linux), we
Subscriptions are offered for the Professional and higher
should use the FireMonkey Application Platform.
editions, and are valid for a period of a year. You can optionally
extend a subscription each year for a further year before the
The FireMonkey Application Platform presents cross-platform
subscription expires. With an active subscription, developers
controls and elements such as forms, dialogs, buttons, menus,
automatically get new editions of Delphi as soon as they are
and so on. It supports 2D and 3D graphics and uses the GPU
released by Embarcadero. They also have the right to three sofor the graphics, freeing the CPU itself for doing the real
called incident support calls with Embarcadero (for example for work (like calculations or database operations).
problems that even your reseller cannot solve for you). A subscription is
The FireMonkey Designer may feel a bit awkward at first, since
especially beneficial if you are a high-end Delphi user, but also
it's not a clone of the VCL Designer. However, remember that
for Professional users if they upgrade at least once every two
FireMonkey is at version 1.0, and there will be many
years.
enhancements (and fixes) in the time ahead.
Major New Feature: X-Platform
The major new feature in Delphi XE2 is the capability for crossplatform development. First of all, the 32-bit Windows compiler
has been extended with a 64-bit counterpart which produces
64-bit Windows applications.
46
COMPONENTS
DEVELOPERS
FireMonkey Example
To demonstrate the FireMonkey Application Platform, let's
create a new application using Delphi XE2. Using File | New
Other, we get into the Object Repository and can see a number
of different FireMonkey project targets for Delphi:
Figure 3
Figure 2
As you can see, this looks like a platform-independent
window, with placeholders for the border icons (on the upper
right, probably because the IDE is still running on Windows), and a
black border and caption. We can now look at the Tool
Palette for a list of FireMonkey controls that can be used.
In order to run the same project, without any changes to the source
code, on Mac OS X, we need to perform some additional steps.
COMPONENTS
DEVELOPERS
47
Figure 4
You need to work through a number of screens (leaving the default
settings intact), after which the Platform Assistant Server will be
installed on the machine, in my case in the
/Users/bob/Applications/Embarcadero/
PAServer directory:
Figure 5
Now, as soon as you start the paserver application itself, it will start a terminal window and ask for a password. Don't panic, this is
not a password that you should have remembered or written down somewhere. Rather, it's the password that you can define here
(at the deployment machine) and that you have to specify at the development machine to allow the development machine access to
the deployment machine (and also to prevent any other visitors from accessing the Mac at port 64211).
Figure 6
48
COMPONENTS
DEVELOPERS
Figure 7
A Target Platform also needs a Remote Profile with the information to connect to the remote target machine. If a remote profile
for the selected platform already exists, then Delphi XE2 will assign it as remote profile. Otherwise, you will be prompted to create
a new remote profile as soon as you try to run the project for OS X.
Figure 8
If you click on [Yes], a dialog will show up with all available Remote Profiles for the OS X Platform. Initially, this list will be empty,
so you need to click on the Add button to create a new Remote Profile, or you need to click on the Import button to
import a Remote Profile (in case you saved and exported one from another development machine for example).
The Add button will display the Create a Remote Profile wizard, were we can specify the name of the remote profile. I typically
call the Mac profiles Mac followed by the last part of the IP-address, so I know which Mac I'm talking about. Mac164 is the Mac
mini running OS X Snow Leopard on which we just installed the paserver:
Figure 9
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
COMPONENTS
DEVELOPERS
49
Figure 10
Click on the Test Connection button to ensure you can talk to the Mac. If the connection is
refused, then you may have to configure the firewall on the Mac to allow the incoming
connection.
Figure 11
If you close this confirmation dialog and go to the next page in the Remote Profile Wizard, a
page is shown which is only relevant for C++Builder developers (who need to cache symbolic
information from the remote machine on the local machine). Delphi developers can just skip
that page and click on the Finish button to create the remote profile.
50
COMPONENTS
DEVELOPERS
Figure 12
This will bring you back to the Select Remote Profile dialog, where you can now finally select the
newly created Remote Profile.
Figure 13
As a result, the Project Manager will now show the Remote Profile next
to the Target Platform in the Project Manager:
Figure 14
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
COMPONENTS
DEVELOPERS
51
Figure 15
And this certainly looks like a native Mac OS X application to
me. If you compare this screenshot to the Windows edition of
the FireMonkey Demo, you will see the same ListBox, Edit and
Button, but the actual look-and-feel is now totally different and
Mac-like, while the Windows edition truly feels like a Windows
application. Of course, these are just simple demo applications,
but you should get the idea
Deployment
When it comes to deployment of your application, especially to
the Mac running OS X, you need to perform a number of steps
again. Using Project | Deployment you can get a Deployment
tab in the IDE that shows the files that need to be deployed to
the Mac OS X machine. There is one gotcha: if you compile a
Release Build, then the project .rsm file will not end up in the
OSX32\Release directory, so this file cannot be deployed. The
Debug Build will produce the .rsm file, which causes it to be
displayed in the list of deployment files: If you connect to the
deployment machine, you can see the Remote Status. The green
arrow will actually deploy the project files. On my Mac, they end
up in the
/Users/bob/Application/Embarcadero/PAServ
er/scratch-dir/Bob-Mac164 where Bob is the name
of the remote user (since the local user is called "bob") and
Mac164 is the name of my Remote Profile.
The FireMonkeyDemo is a 17.6 MB archive that contains all
selected deployment files, and can be run as a stand-alone
application on the Mac. We can also copy it to another Mac
(running at least Mac OS X Snow Leopard) and run it from there.
52
COMPONENTS
DEVELOPERS
Figure 16
expert
COMPONENTS
DEVELOPERS
page
53
COMPONENTS
DEVELOPERS
COMPONENTS
DEVELOPERS
55
COMPONENTS
DEVELOPERS
Finally, you can run the program again and see how the altered
report looks in the preview window.
So, our first example is completed. Summing up, I want to
emphasize that FastReport is able to cope well with the
representation of static images, often used in reports (for
example, a company logo), as well as loadable images with text
in tabular form, provided there is no splitting of the data band.
FastReport Graphic Objects
This section about FastReport's graphic objects is theoretical, but
I suggest you open the FR Designer with a blank report and play
with dropping and modifying graphic objects directly as you read.
This will help you to under-stand what you are reading better.
One of the lower icons in the Designer's left vertical toolbar is
named Draw. When you click this icon, the menu shown in Figure
10 appears.
Figure 10
COMPONENTS
DEVELOPERS
57
COMPONENTS
DEVELOPERS
OK, we have considered an example of constructing a simple chart. In fact, FastReport in combination with the TChart
library allows you to add many different chart types to a report. It's almost impossible to describe all these types. You
can try them for yourself, either using Delphi's demos, or your own databases. This article has described working with
images, graphic objects and simple diagrams in FastReport. I hope that these examples will help you develop high
quality FastReport reports for your own applications.
If you write to me by e-mail at slyubez@gmail.com with your ideas or questions about Delphi I may be able to
discuss the matters you raise in a future article. See you again.
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
COMPONENTS
DEVELOPERS
59
SUMMER OFFER:
LAZARUS
COMPONENTS
DEVELOPERS
Pag 87
Become
expert
DELPHI XE 2 ONLY
Shell Extensions
If you write Windows shell extensions then you have no doubt
been waiting impatiently for a 64-bit Delphi compiler (or moved
long ago to another product that compiled for 64-bit).
Now you can use Delphi to create Windows shell extensions for
64-bit operating systems.
Working with 64-bit in the IDE
This article will just cover working with VCL 64-bit applications
using XE2. The new FireMonkey framework also supports 32bit and 64-bit compilation, as well as being able to target OS X
32-bit (Snow Leopard and above) and iOS (using Free Pascal and
XCode). Creating a new VCL application reveals the first hint of
change. If you look at the Project Manager, you will see that
there is a new node called Target Platforms under the Build
Configuration node.
Figure 1
GB
Home Basic
GB
Home Premium
16
GB
Professional
192 GB
Enterprise
192 GB
Ultimate
192 GB
Library Paths
The Library options page (Tools | Options | Delphi Options |
Library) has been updated to include a Selected Platform combo
box at the top of the page. This lists all valid Target Platforms
Access to gigantic memory spaces is not the only reason for
wanting your application to compile as 64-bit. Being recognised for the IDE (not framework specific). This means that each
as a first class citizen in the operating system has many benefits. platform's paths have to be maintained separately. When
32-bit applications are not able to store to and access some parts switching to the 64-bit Windows platform you will notice
of the system as 64-bit applications usually would.
instances of $(Platform) in the paths displayed. The IDE treats
This includes access to the registry and file system areas. 32-bit
this as a variable and whenever a path contains $(Platform) the
applications are also installed to their own program files folder,
appropriate platform abbreviation is substituted.
called "Program Files (x86)".
Shared resources are stored in a different folder from the
System32 folder called "SysWoW64". Yes, the System32 folder
contains proper 64-bit versions of the system files.
64
Platform Name
Abbreviation
32-bit Windows
Win32
64-bit Windows
Win64
Figure 3
Figure 4
For those with a keen eye, another minor change to the dialog is
the replacement of the "Namespace prefixes" field title with the
title "Unit scope names".
In XE2, all units have a prefix applied to them. These prefixes
are not platform specific so fall outside the scope of this article.
Project Options
As well as being able to set paths for all applications in the
Library section of the options dialog, you need to set your
project's options against a particular platform (and configuration).
COMPONENTS
DEVELOPERS
65
Figure 5
Registering Components for 32-bit and 64bit usage
To target the 64-bit platform, components must be made
available to the 64-bit platform. This can be done in two ways
and just to be sure, I've used both ways for my components.
1. The runtime package (that the design time package
requires) has the 64-bit Windows platform specified.
2. You can use the new Component Platforms Attribute.
type
[ComponentPlatformsAttribute(pidWin32 or pidWin64]
TMyComponent = class(TComponent)
end;
Components
All VCL components shipped with the IDE are available in 32bit and 64-bit versions.
Figure 6
66
COMPONENTS
DEVELOPERS
Figure 8
The first page of the wizard requires you to
name the remote profile. The platform
should already be selected. Once the name
has been entered, select Next.
Figure 9
The second page requires the IP Address of the remote machine as well as a port number and password for connection. The port
number specified (64211) is the default port number used by the Platform Assistant server. If you change this value in the profile,
make sure the PAServer.config file has been updated on the remote machine. When PAServer is launched on the remote machine,
it requests a password - this is the password you should enter in the password field of the profile.
You can test the connection now to verify it all works, provided you have already installed Platform Assistant on the remote
machine (we'll step through that process shortly).
COMPONENTS
DEVELOPERS
67
Figure 10
Figure 11
Modifying Remote Profiles
It is possible to modify remote profiles in the Tools | Options dialog. Select
the Remote Profiles node.
Figure 12
68
COMPONENTS
DEVELOPERS
Typecasting Pointers
Where in the past you got away with typecasting pointers as
Integer or LongInt, now you should be using
NativeInt, IntPtr or LParam. They all mean the same
thing.
Loop Variables
If you need your loop to handle 64-bit values, use NativeInt
for your loop counter.
Handle References
In the past it was common to use Longint or Cardinal for
references to handles. This is something that needs to be
changed for 64-bit. So you now should use either the THandle
or HWND type.
Windows Messages
When working with messages, typecast using WParam,
LParam and LResult.
Before:
After:
Msg.LParam := LongInt(Self);
Msg.LParam := LPARAM(Self);
VCL changes
A large number of records and methods in the VCL have been
updated now to use the correct type. Some methods have even
been deprecated due to these changes, so make sure you inspect
your application's Hints and Warnings. There could be useful
information in there.
Conclusion
If you want your code to run on both 32-bit and 64-bit
versions of Windows, identifying the above code changes will
help to make the transition to 64-bit 'first class citizen' status
as smooth as possible. Hopefully this article gives you a good
guide to beginning your successful journey into 64-bit
development with Delphi XE2.
COMPONENTS
DEVELOPERS
69
COMPONENTS
DEVELOPERS
The Delphi Starter Edition now also has the
ability to do multi tier development for no cost at all.
kbmMW CodeGear Edition v. 3.52.00 contains full
support for Delphi Starter Edition, and provides
access to SQLite in addition to the standard Delphi
Starter Edition database connectivity frameworks,
BDE and IBExpress.
expert
Figure 1.
As usual you will find TkbmMemTable,
TkbmBinaryStreamFormat,
TkbmCSVStreamFormatter and
TkbmThreadDataset (which is available for backwards
compatibility and rarely used) components. And then as something
new - TkbmMemSQL. TkbmMemSQL is in it self a memory
table, that is based on TkbmMemTable, and will be the
container for any results coming from a SQL statement.
Those results can thus be further manipulated or presented in
the same way as a regular TkbmMemTable instance by using it
as a source of data for some other procedure or connect it to
data aware controls. To make TkbmMemSQL tick, we need to
give it two things to work with data and a SQL statement. The
raw data is provided for it via other datasets. Currently
kbmMemTable is supported as a data provider, but kbmSQL
has been designed with extesibility in mind, since other data
providers may be added later on.
So let's start with a sample application.
72
COMPONENTS
DEVELOPERS
Figure 2.
I have added a kbmMemTable (for the raw data), a kbmMemSQL
component for doing our SQL, and a couple of grids to show
the raw and result data, and finally a button that we will click to
execute the SQL.
Ill make it simple and just put the code we need into the
Execute SQL button.
uses
// Make sure to include this unit to have support for kbmMemTable registered in kbmSQL.
kbmSQLMemTableAPI;
procedure TForm1.Button1Click(Sender: TObject);
begin // Prepare some raw data to work on.
kbmMemTable1.Reset;
kbmMemTable1.FieldDefs.Add
('StringField1',ftString,50);
kbmMemTable1.FieldDefs.Add
('StringField2',ftString,50);
kbmMemTable1.FieldDefs.Add
('NumberField1',ftFloat);
kbmMemTable1.FieldDefs.Add
('NumberField2',ftFloat);
kbmMemTable1.CreateTable;
kbmMemTable1.Open;
kbmMemTable1.AppendRecord
(['String 1','AnotherString 1',120,10.10]);
kbmMemTable1.AppendRecord
(['String 2','AnotherString 1',15,20.10]);
kbmMemTable1.AppendRecord
(['String 3','AnotherString 1',10,10.10]);
kbmMemTable1.AppendRecord
(['String 4','AnotherString 2',33,10.10]);
kbmMemTable1.AppendRecord
(['String 5','AnotherString 2',12,10.10]);
kbmMemTable1.AppendRecord
(['String 6','AnotherString 2',99,20.10]);
kbmMemTable1.AppendRecord
(['String 7','AnotherString 3',19,10.10]);
kbmMemTable1.AppendRecord
(['String 8','AnotherString 3',143,20.10]);
kbmMemTable1.AppendRecord
(['String 9','AnotherString 3',199,10.10]);
// Register kbmMemTable1 as source for kbmMemSQL1.
// Notice that we have given kbmMemTable1 a name that identifies it in SQL (table1).
kbmMemSQL1.Tables.Clear;
kbmMemSQL1.Tables.Add('table1',kbmMemTable1);
// Prepare some simple SQL.
kbmMemSQL1.ExecSQL('select * from table1');
end;
Result:
Figure 3.
Notice that kbmSQL operates with case insensitive field/table
names, and thus the field names are uppercase by default.
Lets play a little with the different syntaxes kbmSQL supports.
Change the SQL code in the ExecSQL call to try them out, one
by one. Remember to escape (duplicate) single quotes (') when
used in strings in Delphi. You can also use double quotes ()
which do not need to be escaped (duplicated) in Delphi strings.
Figure 6.
Sorting ascending:
select * from table1 order by NumberField1
Result:
I've already shown the SQL syntax for how to get data from all
fields and all records. See above.
Specific fields:
select StringField1,NumberField2
from table1
Result:
Figure 7.
Sorting descending:
select *
from table1 order by NumberField1 desc
Result:
Figure 4.
Field alias with descriptive text:
select
Figure 8.
StringField2 as MyField (My string field),
NumberField2 as Numbers from table1 Filtering:
select *
Result:
Result:
Figure 5.
Figure 9.
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
COMPONENTS
DEVELOPERS
73
Figure 10.
Grouping:
select NumberField2
from table1
where NumberField1 between 10 and 20
select NumberField2,
count(NumberField2),
sum(NumberField1)
from table1 group by NumberField2
Result:
Result:
Figure 15.
Figure 11
Inserting new data using SQL:
And
Result:
Figure 16.
And the SQL LIKE operator is also supported. LIKE
matches string masks using * and ? as wildcards. * matches
any number of arbitrary characters and ? matches exactly
one arbitrary character. Eg.
select * from table1
where StringField1 like *6*
Result:
Figure 12.
Changing data using SQL:
update table1 set NumberField1=
NumberField1 + 100 where NumberField2<20
Figure 17.
also contains a large predefined set of functions
that can be used in expressions. To use them, you need to
add kbmSQLStdFunc to the uses clause of the application.
The functions include:
kbmSQL
Figure 13.
Deleting data using SQL:
delete from table1 where NumberField2<20
COMPONENTS
DEVELOPERS
Result:
Figure 18.
Notice that the default field name is now generated as F1. You
from table1
can of course redefine the name using the AS syntax as shown
Result:
earlier on. If you would like to use an expression function that is
currently not available in kbmSQL its very easy to extend
kbmSQL with new functionality.
Lets say we want to define a function that calculates Pythagoras'
hypotenuse value:
PYTHAGORAS(x,y) = sqr(x*x+y*y)
Then we create a new unit and add it to the project. Lets call it
myfunctions.pas.
Put the following into it:
interface
uses
DB, kbmSQLElements, kbmSQLFuncAPI,
Math, Classes, SysUtils;
Figure 19.
Obviously we could have used other field values or
expressions instead of the constant values 10 and 20,
used as arguments for Pythagoras.
implementation
procedure CheckArgs(const AArgs:TkbmSQLNodes;
const ACnt:integer);
begin
if AArgs.Count<>ACnt then
raise
Exception.Create('Invalid number of
arguments. Expected '+inttostr(ACnt));
end;
function SQLPythagoras(ASender:TObject;
AOperation:TkbmSQLFunctionOperation;
AArgs:TkbmSQLNodes; var AResult:variant):boolean;
var
d1,d2:double;
begin
CheckArgs(AArgs,2);
if AOperation=foExecute
then
begin
d1:=AArgs.Node[0].Execute;
d2:=AArgs.Node[1].Execute;
AResult:=sqrt(d1*d1+d2*d2);
end
else
AResult:=AArgs.Node[0].Width;
// Let it take width after first argument for function.
Result:=true;
end;
initialization
kbmSQLFunctionRegistrations.RegisterFunction
('PYTHAGORAS',@SQLPythagoras,ftFloat);
end.
To receive a copy of
the printed magazine
you need to
order a subscription
from our website:
www.blaisepascal.eu
COMPONENTS
DEVELOPERS
75
High-Level Multithreading
starter
expert
By Primoz Gabrijelcic
where you'll find pointers to articles about the OTL and a web
forum where you can raise your questions.
Async
76
COMPONENTS
DEVELOPERS
procedure TfrmJoin.btnAnonymousClick(Sender:
TObject);
begin
btnJoinAnon.Enabled := false; Update;
Parallel.Join(
procedure
begin
Sleep(2000);
end,
procedure begin
Sleep(3000);
end
);
btnJoinAnon.Enabled := true;
end;
Future
Future is a tool that enables you to start a background
);
calculation and then forget about it until you need the result of
end;
the calculation. To start a background calculation, you simply
Further information about TaskConfig can be found on my
create an IOmniFuture instance of a specific type (indicating the
type returned from the calculation).
blog: http://www.thedelphigeek.com/2011/04/
Future := Parallel.Future<type>(calculation);
configuring-background-otlparallel.html
Calculation will start in the background, and the main thread
Join
will continue with its work. When the calculation result is
Join is another very simple tool which allows you to start
needed, you simply query Future.Value. If the calculation has
multiple background tasks and wait until they have all
already completed its work, Value will be returned immediately.
completed. No result is returned at least directly, since you can If not, the main thread will block until the background
always store the result in a shared variable. If your code returns calculation is done.
a result you are better off using Future or ForkJoin.
The example below starts a background calculation that returns
The simple demonstration of Join (shown below) starts two tasks the number of prime numbers in the range 1..1,000,000. While
one sleeps for two and the other sleeps for three seconds. When the calculation is running, it uses the main thread for creative
you run this code, Parallel.Join will create two background
work outputting numbers into a listbox and sleeping. At the
threads and run RunTask1 in the first thread and run RunTask2
end, the calculation result is returned by querying future.Value.
in the second thread. It then waits for both threads to finish, and
only then will the main thread execution continue.
procedure
);
end
TfrmOTLDemoFuture.btnCalcFutureClick(Sender:
procedure TfrmJoin.btnParallelClick(Sender: TObject); TObject);
const
begin
CMaxPrimeBound = 1000000;
btnJoinMethods.Enabled := false; Update;
var
Parallel.Join([RunTask1, RunTask2]);
future
: IOmniFuture<integer>;
btnJoinMethods.Enabled := true;
i
integer;
:
end;
numPrimes: integer;
begin
procedure TfrmJoin.RunTask1;
// create the background calculation
begin
future := Parallel.Future<integer>(
Sleep(2000);
function: integer
end;
begin
Result := CountPrimesTo(CMaxPrimeBound);
procedure TfrmJoin.RunTask2;
end
begin
);
Sleep(3000);
// simulate another task
end;
for i := 1 to 10 do begin
lbLog.Items.Add(IntToStr(i));
Join ensures compatibility with single-core computers. If you
Sleep(20);
run the above code on a single-core machine (or if you limit the
lbLog.Update;
process to one core of a multicore machine) it will simply execute the
end;
tasks sequentially, without creating a thread.
// get the result
Log(Format('Num primes up to %d: %d',
Join accepts anonymous methods. The above demo could also
CMaxPrimeBound, future.Value]));
be coded as a single method executing two anonymous methods. [end
;
COMPONENTS
DEVELOPERS
77
nodeList := TList.Create;
//
Parallel.ForEach<integer>(nodeList).Execute(
procedure (const elem: integer)
begin
if IsPrime(elem) then
outQueue.Add(elem);
end);
www.thedelphigeek.com/2010/06/omnithreadlibrar
y-20-sneak-preview-1.html
IOmniFuture<T> = interface
procedure Cancel;
function IsCancelled: boolean;
function IsDone: boolean;
function TryValue(timeout_ms: cardinal;
var value: T): boolean;
function Value: T;
end;
Also, there were some changes after the 2.1 release, mostly
relating to exception handling:
http://www.thedelphigeek.com/2011/07/
life-after-21-exceptions-in.html.
ForEach
Parallel For (actually called ForEach because For would clash with the
reserved keyword for) is a construct that enumerates in a parallel
fashion over different containers. The most typical usage is
enumerating over a range of integers (just as with the classical for),
but it can also be used similarly to the for..in construct for
enumerating over Delphi (or Windows) enumerators.
The following very simple example loops over an integer range
and increments a global counter for each number that is prime.
This is another way to count the number of primes in the range
1..CHighPrimeBound.
Pipeline
The Pipeline construct implements high-level support for
multistage processes. The assumption is that the process can be
split into stages (or sub-processes) connected by data queues.
Data flows from the (optional) input queue into the first stage,
where it is partially processed and then emitted into an
intermediate queue. The first stage then continues execution,
processing more input data and outputting more output data.
This continues until the entire input has been processed.
The intermediate queue leads into the next stage which does the
processing in a similar manner and so on and so on.
At the end, the data is output into a queue which can then be
read and processed by the program that created this multistage
process. Overall a multistage process functions as a pipeline
data goes in, and eventually data comes out.
78
www.thedelphigeek.com/2011/02/parallel-forimplementation-3-output.html
TIME
STAGE 1
STAGE 2
STAGE 3
--STAGE N
The various pipeline stages are shown below. The first stage
ignores the input (which is not provided) and generates
elements internally. Each element is written to the output queue.
procedure StageGenerate(const input, output:
IOmniBlockingCollection);
var
i: integer;
begin
for i := 1 to CNumTestElements do
if not output.TryAdd(i) then Exit;
end;
The following three stages read data from their input (by using a
for..in loop), and output modified data into their output queue.
For..in will automatically terminate when a previous stage
terminates and its input queue runs out of data (that's a feature of
the IOmniBlockingCollection enumerator).
The latter version takes two parameters an array of processing
As you can see from the code, values in input/output
stages and an optional input queue. An input queue can be
queues are not integers, but TOmniValue s (declared in the
used to provide initial data to the first stage. It is also completely
OtlCommon unit), which is an OTL version of Delphi's
valid to pass 'nil' as the input queue parameter and to run the
Variant.
first stage without any input.
COMPONENTS
DEVELOPERS
79
forkJoin := Parallel.ForkJoin<integer>;
The code below shows how Fork/Join can be used to find the
maximum element in an array. At each computation level,
ParallelMaxRange receives a slice of original array.
If it is small enough, a sequential function is called to determine
the maximum element in the slice.
Otherwise, two sub-computations are created, each working on
one half of the original slice. Results from both subcomputations are aggregated by calling the Max function,
and the result is returned to the upper level.
The last stage also reads data from its input but outputs only
one number a sum of all input values.
procedure StageSum(const input, output:
IOmniBlockingCollection);
var
sum : integer;
value: TOmniValue;
begin
sum := 0;
for value in input do
Inc(sum, value);
output.TryAdd(sum);
end;
Fork/Join
ForkJoin is the most complicated high-level parallel construct in
the OTL. It is an implementation of the divide-and-conquer
technique. In short, ForkJoin allows you to execute multiple
tasks, wait for them to terminate and collect their results.
The trick here is that subtasks may spawn new subtasks and so
on ad infinitum (probably a little less than infinite, or you'll run out of
stack ). For optimum execution, ForkJoin must therefore
guarantee that the code is never executing too many background
threads (the optimal value is usually equal to the number of cores in the
system), and that those threads don't run out of work.
ForkJoin subtasks are in many way similar to Futures. They offer
slightly less functionality (there is no cancellation support) but they
are enhanced in another way when a ForkJoin subtask runs
out of work, it will start executing some other task's workload,
keeping the system busy.
A typical way to use Fork/Join is to create an
begin
if (right - left) < CSeqThreshold then
Result := SequentialMax(left, right)
else begin
mid := (left + right) div 2;
computeLeft := Compute(left, mid);
computeRight := Compute(mid + 1, right);
Result := Max(computeLeft.Value,
computeRight.Value);
end;
end;
IOmniForkJoin<T> instance
80
COMPONENTS
DEVELOPERS
expert
Flexibility and the possibility of customization is important in every application. Your application needs to be
flexible enough to fit the requirements of all your customers, even if you have a large, heterogeneous customer base.
You can make your application customizable by allowing features to be adaptable depending on the software
configuration. You also need to provide dialogs so users can configure the available options. All this takes
development time and, worse still, requires that you define in advance which features will be customizable and
which won't be. If a customer unexpectedly asks you if he can change some font colour, you can point him to your
application's font options dialog unless you never imagined any customer would ask for that and you just don't
have a font options dialog.
That's one of the purposes of a scripting system: to allow for customization. By adding scripting to your
application, you raise your application's flexibility to a new level, by letting users (or your support team) customize
the application using scripts, without having to worry about pre-preparing the application for a particular
customization. In the example above, you could have just prepared your application to run a script before the form
opens, and then the user could customize the form the way he wants including setting the colour with a simple
Font.Color := clBlue;.
The purpose of this article is to show how scripting increases your application's flexibility. Not only that our
intention is to show how quickly you can do this using TMS Scripter Studio Pro and all its built-in features that are
almost plug-and-play.
Subject: The ActionBars demo
To illustrate scripting usage we will take an existing application and customize it, since this is what happens in real life. You don't
often build an application designing it to be fully customizable. Rather, you have already built your working application and
subsequently wish to make it customizable. So we will use the ActionBars demo that comes with Delphi's sample applications.
This demo is intended to show how to use action bars, but in fact it's just a small RTF editor. What we will do is build a macro
system so users can create new macros and run them, yielding similar functionality to Microsoft Word's macro feature.
Figure 1
Preparing the GUI
Before going into scripting itself, let's just quickly modify the application's graphical user interface so that users can create and run
macros. Basically we will just add a new menu called Macros with two options: Edit Macros and Execute Macro.
The first option allows users to create and edit macro content, and the second option allows users to choose a macro to be executed.
Figure 2:
Here is a screenshot of our
modified ActionBars demo.
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
81
Figure 3
TIDEEngine is the core of scripting projects. It handles opening, saving and running projects among other things.
It has a property named Scripter, which we must point to the TIDEScripter component. This is the scripting engine, which
effectively runs the scripts and holds information about supported types. Finally, TIDEDialog is a high-level GUI dialog which
opens a ready-to-use IDE for us to edit and debug our scripts. It must be linked to the TIDEEngine component through its
Engine property. In addition to these components, we should also add some basic types to our scripting system.
For example, we want our users to be able to call the IntToStr procedure, to be able to use edits and combos in the script form,
so we will add some imported libraries to our application. Those are libraries already deployed with TMS Scripter Studio, and they
include almost all the VCL imports. We will add basic ones like Forms, and SysUtils.
To do that, you just need to add some units to the uses clause of the ActionBars project:
implementation
uses
ap_SysUtils, ap_Windows, ap_Classes, ap_Forms, ap_Dialogs, ap_StdCtrls, ap_Controls,
ap_Graphics, TypInfo;
Once we do that, we are able to run scripts. Now we just need to integrate the scripting system with our new GUI. We will add
code to our two newly created actions, acEditMacros and acExecuteMacro. Here is how (see listing ):
procedure TForm1.acEditMacrosExecute(Sender: TObject);
begin
IDEEngine1.NewProject;
IDEDialog1.Execute;
end;
procedure TForm1.acExecuteMacroExecute(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
IDEEngine1.OpenProject(OpenDialog1.FileName);
IDEEngine1.RunProject;
end;
end;
It's as simple as that. In acEditMacrosExecute, we just call the TIDEEngine.NewProject method in order to clear any
macro project that is in memory, then call TIDEDialog.Execute to show the IDE to edit the macros. In the
acExecuteMacroExecute method, we let the user choose a script file, then load the project using the TIDEEngine.OpenProject
method and later run it using TIDEEngine.RunProject.
82
Figure 4
This blank project just creates and displays an empty form. Let's just remove Unit2 from the project (this holds the empty form),
by selecting Unit2, and then choosing menu option File | Remove from project.
Then we change the source code of Unit1 to just show a Hello world message:
Figure 5
We can just use File | Save All menu option to save all the files. We can rename Unit1 as HelloWorldUnit.psc and Project1 as
HelloWorld.ssproj, and we're done, our new macro is created.
Executing the Hello World Macro
Now in our ActionBars main form we can choose the Macros | Execute Macro menu option. It will display a file dialog from
which we can choose our HelloWorld.ssproj project file, and then execute it:
Figure 6
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
83
Figure 7
Figure 8
Full integration with the application
This is already a start, but we can't do many interesting things with a stand-alone scripting system if we don't integrate it into our
application. This is a key part of a good flexible system. In summary, what we will do is make parts of our application available
from scripting. Usually and ideally you would have a collection of business objects and make them available so end-users can
manipulate those objects from scripting.
In the present case, our main purpose is to get the user to interact with the text editor itself. So we will make the TRichEdit
available to the script. We will create an InitScripter method and we will call it from the FormCreate event:
procedure TForm1.FormCreate(Sender: TObject);
<snip>
begin
InitScripter; // New line added to FormCreate
<snip>
procedure TForm1.InitScripter;
begin
IDEScripter1.DefineClassByRTTI(TRichEdit, '', [mvPublic, mvPublished], true, roOverwrite);
IDEScripter1.AddComponent(RichEdit1);
end;
That's all we need! In InitScripter, we make the scripter aware of the TRichEdit class, most of its methods and properties, and
also subclasses, like TTextAttributes. The second line makes the script aware of a TRichEdit instance we have in our
application, RichEdit1,so it can be used from the macros.
Note that this code uses a new feature that is only supported in Delphi 2010 and up, as a result of the new RTTI system.
TMS Scripter Studio Pro is very powerful and flexible. You can add any class, method or property in your application manually to
the scripting system. You could add each method of TRichEdit individually, and you could add other instances (for example we
could have added the toolbar so users could manipulate the toolbar).
But this is the most interesting one, because it's very straightforward at only two lines of code!
This allows us to build much more interesting macros, with full code completion and parameter hints!
84
Figure 9: Now our users can just select a piece of text in the editor:
85
As a final example, we will build a GotoLine macro to illustrate how to use the Form Designer to improve flexibility
even more. We will build a macro that firstly displays a form (so the user can input the line number which he wants the
cursor to be positioned at), and then proceeds to change the cursor position to the given line number. In this macro we
will use two scripts, one for the form, and the other to display the form and execute the operation. The following
screenshots show all the files:
Figure 12
Figure 13
Figure 14
86
Figure 17
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
87
88
expert
LAZARUS
COMPONENTS
DEVELOPERS
Vec.Clear;
Vec.StartPath(0, 0);
Vec.AddLineToPath(10, 10);
Vec.AddLineToPath(20, 30);
Vec.AddLineToPath(30, 20);
Vec.EndPath();
Vec.WriteToFile('polyline_1'
+ cExtension, cFormat);
finally
Vec.Free;
end;
end.
// bezier_1.svg
Vec.Clear;
Vec.StartPath(0, 0);
Vec.AddLineToPath(10, 10);
Vec.AddBezierToPath(10, 20, 20, 20, 20, 10);
Vec.AddLineToPath(30, 0);
Vec.EndPath();
Vec.WriteToFile('bezier_1' + cExtension, cFormat);
// multi_test_1.svg
Vec.Clear;
Vec.StartPath(0, 20);
Vec.AddLineToPath(30, 30);
Vec.EndPath();
Vec.StartPath(0, 0);
Vec.AddLineToPath(100, 0);
Vec.AddLineToPath(100, 100);
Vec.AddLineToPath(0, 100);
Vec.AddLineToPath(0, 0);
Vec.EndPath();
Vec.StartPath(0, 0);
Vec.AddLineToPath(10, 10);
Vec.AddBezierToPath(10, 20, 20, 20, 20, 10);
Vec.AddLineToPath(30, 0);
Vec.EndPath();
Vec.AddText
(10, 10, 0, '10,10 Some text in english.');
Vec.AddText(20, 20, 0, '20, 20 Mwic, czesc,
Wlosku,Parabns.');
Vec.AddText(30, 30, 0, '30, 30 ');
Vec.WriteToFile('multi_test_1'
+ cExtension, cFormat);
COMPONENTS
DEVELOPERS
91
Modifying documents
The methods shown above are adequate for adding new
elements to a document, but not for modifying existing
elements already in a document. For that you have to use other
methods. Each instance of the TvVectorDocument class
contains a list of elements already in the drawing. Everything is
considered an element: paths, texts and other entities. To modify
an element you have to obtain it with the GetEntity() routine
which takes the index of the element in this list as a parameter.
Note that the index might change if elements are removed from
the list. The index goes from zero to GetEntityCount() 1, and
with this information it is possible to iterate through all
elements in the document. There are also two very similar
methods which list only the path elements called GetPath() and
GetPathCount(). They work very similarly and can be used to
obtain just the path elements of the document. They are
provided mostly for backwards compatibility.
Listing 5.
The methods to obtain elements of a document
function GetPath(ANum: Cardinal): TPath;
function GetPathCount: Integer;
function GetEntity(ANum: Cardinal): TvEntity;
function GetEntityCount: Integer;
COMPONENTS
DEVELOPERS
COMPONENTS
DEVELOPERS
93
of
vfSVG
.svg
vfEncapsulated
PostScript .eps
vfDXF
.dxf
vfCorelDRAW
.cdr
vfGCodeAvisoCNC
PrototipoV5
.g
94
COMPONENTS
DEVELOPERS
95
You can pass null for this parameter to stop the sound playing.
We want to use the asynchronous flag here (snd_async)
because then the call will exit immediately, allowing the
animation to proceed. You might think that a synchronous call
would mean the animation and sound were synchronised. But
here it does not mean that! Theoretically you could achieve the
same effect in another way: cut up the program time into tiny
slices and quickly alternate a bit of sound and a bit of
animation.
expert
The simple algorithm we started with is largely selfdocumenting, particularly since I added suitable comments
where I felt an explanation was needed. Now the fun is that the
compiler is not interested in the beauty of the code, nor in its
length. It will process whatever you have written, whether in
short or long Pascal statements.
I would advise you to try both the simpler (more extended)
We will start of course with the simplest presentation of the
version as well the shorter compressed version. You will learn
algorithm, written as a sequence of instructions so as to be
from looking at both. By the way, for lovers of multithreading:
readily understandable. For the alternative compressed
Primoz
Gabrijelicic has written a further article about High
algorithm we will organise the instructions differently and make
Level
Multithreading
in this issue (page74).
them more compact, saving the code in a separate unit. This can
lead to a noticeable reduction in the number of lines of code.
Although better in some ways it is not so readily understood by You can download both of these nightingale projects:
one unit is for Delphi the other for Lazarus. But they are
beginners (and we were all beginners once).
interchangeable without a problem. The image data files are
My personal opinion is that abbreviated code, while often very
included with the projects, so you can see exactly how the
beautiful, can be hard to understand later on, especially if you
animation works.
have not documented it well at the time of writing. The
algorithm in this project uses an array, a Windows API call
The sound and action have to be triggered by an event, so we
(sndPlaySound) and a timer object. Nothing too difficult.
have made that event the starting point. To animate an image
requires drawing it in a sequence of subtly different positions.
Drawing this sequence of images in quick succession gives the
illusion of movement. So for an animated bird I found a
photograph of a real nightingale, from which I created a series
of sketches incorporating subtle variations.
COMPONENTS
DEVELOPERS
end.
COMPONENTS
DEVELOPERS
97
barnss en
DEVELOPMENT &
DATABASE
TOOLS
COMPONENTS
DEVELOPERS
99
FireMonkey (continuation 1)
requires GPU)
expert
TAdsQuery
This TDataset descendant can be used to execute SQL
statements. It is equivalent to the TADOQuery, TIBQuery or
TSQLQuery components in Delphi and Lazarus.
TAdsStored
This component can be used to execute or retrieve data from a
stored procedure. Again, it is the equivalent of existing stored
procedure components in Delphi, tuned for use with the
Advantage Database server.
TAdsSettings
This component can be used to manipulate the local database
engine settings. This is something which can also be done in
custom code, but the component makes the process easier.
TAdsDictionary
This component encapsulates an Advantage Data Dictionary.
More is written about data dictionaries in what follows.
There are several other Advantage components as well, but
those listed above are the most important.
Creating a database
Advantage Databases have no single database file as in Firebird
or MS-SQL Server. Basically, an Advantage database is a
directory with a collection of table files. Therefore, it is not
really necessary to create a database, other than creating a
directory to contain the table files. This kind of database is
termed 'Free Tables', and is in fact very similar to a Paradox
database: namely a set of flat-file tables that are connected only
through the logic of program that uses them. For simple
systems, this may well be enough.
However, a data dictionary can be associated with a database.
The data dictionary contains metadata about the tables in the
database: it describes constraints on tables, relations between
tables, stored procedures, user access rights and many more such
attributes all the kinds of features one looks for in an RDBMS
system.
A data dictionary can be created in one of 3 ways:
1. Through a low-level API call of the Advantage Client
Engine library (AdsDDCreate).
2. Through a method of the TAdsDictionary class.
The class is marked as deprecated, but it is in fact the easiest
way to create a data dictionary in code.
3. Using the Advantage Data Architect.
This is a program which has to be downloaded and installed
separately from the ADS TDataset support. It comes
with full source, which is an invaluable source of
information on using the ADS Delphi components.
Using the Advantage Data Architect is in fact the fastest way to
create an Advantage Database. Under the 'File' or 'Connection'
menu items you will find a 'New connection Wizard'. The
wizard starts by asking whether a connection should be made to
an existing database, or whether a new database is to be created.
COMPONENTS
DEVELOPERS
101
Creating tables
Once the data dictionary has been created you can add tables to
it. There several ways to do this:
1.
Using the Data Architect to structure the table.
2. Using the Data Architect to import existing data into
a new table.
3. Use Delphi code based on the TAdsTable component
which contains a table-creation method.
4. Using a DDL SQL statement in the Data Architect or
from a Delphi program.
The first option is certainly the easiest. A simple set of tables to
contain Blaise Pascal Magazine's issues and their contents can be
modelled in less than 5 minutes, and is shown in Figure 2.
A particularly nice thing about the Data Architect is that it can at will - write code (in several programming languages) to
recreate the modelled table. It can do the same with SQL,
writing SQL statements which will recreate the whole data
dictionary or a simple table. This is a useful feature also found in
the Lazarus Database Desktop, for instance.
COMPONENTS
DEVELOPERS
begin
With QCreate do
begin
SQL.Text:=SCreateSQL;
ExecSQL;
end;
end;
values(Cursor1.title,cursor1.Author,Cursor1.is
sue);
end if;
end while;
Close Cursor1;
END;
procedure TBlaiseModule.CreateContentsSQL;
COMPONENTS
DEVELOPERS
103
Accessing Data
Now that the database has been created, accessing the data can
be done using one of three possible components:
TAdsTable This TDataset descendant can be used to
view all table data. It supports filtering and searching
using the standard TDataset properties and methods
(Filter and Locate). Two tables can easily be joined
together in a master-detail relationship using the
MasterSource and MasterFields properties, a well
known mechanism from the Delphi BDE and TDBF
components.
TAdsQuery This component can be used to run a
standard SQL SELECT statement if data from several
tables needs to be shown in a single dataset, or it can
be used to display a selection of records from a single
table.
COMPONENTS
DEVELOPERS
To receive a copy of
the printed magazine
you need to
order a subscription
from our website:
www.blaisepascal.eu
Parameterised queries
The same setup for browsing data can be coded using
TAdsQuery components. In fact, they are even better suited for
such situations: the TAdsQuery component supports
parameterised queries, and this mechanism can be used to create
master-detail relationships as well.
A parameterised query is one which contains a named
parameter. Here is an example:
SELECT
Issues.Date, Issues.Issue, Issues.Theme,
Contents.Title,Contents.Author,Contents.
Abstract,Contents.StartPage
FROM
Contents
Left Join Issues on
(Contents.Issue=issues.Issue)
WHERE
Keywords like :Keyword
COMPONENTS
DEVELOPERS
105
COMPONENTS
DEVELOPERS
106
procedure
TSearchStoredProcForm.BSearchClick(Sender:
TObject);
begin
With BlaiseModule.SPSearch do
begin
Close;
Params.ParamByName
('aKeyWord').AsString:=EKeyword.Text;
Open;
end;
end;
Conclusion
Advantage Database Server is a prime candidate for an
embedded SQL database solution because it is so easy to
deploy (two DLLs suffice for most installations). Added to
this, its excellent support for stored procedures and embedded
functions makes it very suitable for deployment with Object
Pascal applications. The free licensing scheme, easy upgrade
path to a complete client/server solution with remote
database, and last but not least its strict adherence to SQL
typing make it a preferred option compared to alternatives
such as sqlite.
expert
COMPONENTS
DEVELOPERS
107
Figure 1
You use this dialog box to either enter a connection string (or
build it), or to select a data link file. Data link files are discussed
later in this article, so I will focus here on building the
connection string. To build the connection string, click the
button labeled Build. Windows responds by displaying the
Provider page of the Data Link Properties dialog box, shown in
Figure 2.
The Data Link Properties dialog box has four pages. You use the
Provider page of this dialog box to select which installed OLE
DB Provider you want to use to connect to data. Windows ships
with a rather generous collection of OLE DB Providers,
including providers for SQL Server, Oracle, and MS Access.
These can be seen in Figure 2.
If you have installed additional OLE DB Providers, those will
appear on this page as well As you can see from Figure 2, I have
two custom OLE DB Providers: One for the Advantage
Database Server and another for IBM's AS400.
Figure 3
Here you select the database to which you want to connect.
If you are using an OLE DB Provider that connects to a remote
database, you might have additional options, such as selecting
the server on which your database is installed. In this case,
you can use the ellipsis button to select the Microsoft Access
.mdb file and optionally provide a username and password
(assuming that the database is encrypted).
To select the MS Access database installed by Delphi, click the
ellipsis button () and navigate to C:\Program Files
(x86)\Common Files\CodeGear Shared\Data
(this directory might be different if you are using Delphi XE2 or later or
Delphi 2005 or earlier). Select the dbdemos.mdb file located in
that directory and select Open.
You may have to supply additional information, but not in this
case, since the dbdemos.mdb file is not encrypted.
You are now ready to test your connection.
Click the Test Connection button. If everything is ok, you will
see a dialog box similar to the following.
Figure 2
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
COMPONENTS
DEVELOPERS
109
Figure 4
Select OK to close this dialog box, then select Next or click the
Advanced tab to continue to the Advanced page of the Data
Link Properties dialog box, shown in Figure 5.
Figure 6
Here is where you can see that OLE DB Provider properties,
and the corresponding connection string values, are specifically
associated with the OLE DB Provider you selected. If you have
selected any other OLE DB Provider from the Provider page of
the Data Link Properties dialog box the properties displayed on
the All page would be significantly different. Examples of
properties that you might encounter here include whether or not
to employ a connection pool, the connection pool size,
connection timeout, and transaction isolation level, among
others.
Again, we don't need to make any additional changes in this
case, so click OK to accept the connection string you have built
and return to the ConnectionString dialog box. The newly
constructed connection string will now appear in the
Connection String field, as shown here.
Figure 5
You use this page of the Data Link Properties dialog
box to define additional connection information,
including what locking mechanism you want to use to
connect to the database. In this case we don't need to
make any changes. Click the All tab to display the All
page of the Data Link Properties dialog box, shown in
Figure 6.
Figure 7
110
COMPONENTS
DEVELOPERS
Figure 8
It is worth noting that all of an OLE DB Provider's properties
have default values, and the values that appear in the connection
string are only for those properties where you did not select the
default property value.
Using Data Link Files
A data link file is a file with a .udl extension which contains the
information necessary to connect to a data store using an OLE
DB Provider. In most cases, this file is stored in a directory
defined in the Windows registry. Fortunately, the ADODB unit
in Delphi also surfaces a function, DataLinkDir, which returns
the fully qualified path to this directory. In a typical Windows 7
installation, this directory is c:\Program Files
(x86)\Common Files\System\OLE DB\Data Links for
32-bit applications, and c:\Program Files\Common
Files\System\OLE DB\Data Links for 64-bit
applications.
COMPONENTS
DEVELOPERS
Pag 111
Once the data link file has been created, you can use the
ConnectionString dialog box to select it, as shown here.
Figure 9
Figure 10
While you can still use installed and configured ODBC drivers
from the Borland Database Engine, the preferred way to use
ODBC drivers is to use them through Microsoft's OLE DB
Provider for ODBC Drivers.
Once you have chosen the Microsoft OLE DB Provider for
ODBC Drivers on the Provider page of the Data Link
Properties dialog box (see Figure 2), you can advance to the
Connection page to select the Data Source Name (DSN)
associated with your installed and configured ODBC Driver.
Installation of an ODBC driver is performed by the software
provided by the ODBC Driver publisher. Configuration of the
DSN is something that you must do (or can be done by your
installation program, if it is ODBC aware).
If you are running 32-bit Windows, you select the Data Source
(ODBC) applet from the Administrative Tools applet on the
control panel.
If you are running 64-bit Windows, and want to use a 32-bit
ODBC driver from a 32-bit Delphi executable, things are a little
more complex. Instead of using the Data Source (ODBC)
applet from Administrative Tools (which only works with 64-bit
ODBC drivers), you must run the 32-bit version of the ODBC
Data Source Administrator.
112
COMPONENTS
DEVELOPERS
expert
By Bob Swart
DELPHI XE 2
Figure 2
This choice will create a BindExprItemsLabel11 instance
for the LiveBindings property of the Label1 control. We
need to set a number of properties: the SourceComponent
should point to Edit1, and the Category should be set to
Binding Expressions already. Then, we should define the
ClearExpressions (to define what the Label will show
initially) and the FormatExpressions (to define how the
Label should mimic the Edit value).
However, before we define these two Expressions, lets first take
a look at the form, and notice that we suddenly have an extra
non-visual component: the TBindingsLists. If you
double-click on the TBindingsList component, you'll get a
list of all LiveBindings, and can select them individually to edit
the properties (including the FormatExpressions and
ClearExpressions.
Figure 1
There are eight different LiveBinding types available here,
divided into three logical groups. The Binding Expressions
group consists of a simple TBindExpression (for a
binding expression) and a TBindExprItems (for keeping
controls synchronized).
To receive a printed copy of the magazine
you need to go to our website to place your order
COMPONENTS
DEVELOPERS
113
Figure 3
Let's define the FormatExpressions now. Click on the
ellipsis for the FormatExpressions property in the Object
Inspector, which will show another window where we can specify
the Control Expression (from the TLabel) as well as the Source
Expression (from the TEdit).
Figure 4
Here, we can specify property names, like the Caption of the
TLabel and the Text property of the TEdit. But we can
also stipulate that the TLabel should show the
UpperString version of the Text. Right-click in the
FormatExpressions list and Add a new item. Set the
SourceExpression to Text (from the Edit1) and the
ControlExpression to Caption (from the TLabel)
for the same effect. I totally agree, but I just wanted to start with
a simple example, before showing some LiveBinding examples
that may not be very trivial to implement otherwise.
But before we move to other examples, let's try to extend this
example a little bit. Instead of simply using the Text as
SourceExpression, we can also add expressions here, like
"UpperCase:" + UpperCase(Text)
COMPONENTS
DEVELOPERS
Figure 8
Figure 6
We should create a new LiveBinding for the StringGrid, and this
time select the TBindGridLink type. The
SourceComponent of the new LiveBinding should point to
the TBindScopeDB component (connected to the dataset).
Then, we can define the ColumnExpressions using the ellipsis in
the Object Inspector to show the ColumnExpressions dialog.
Individual TColumnExpression items have a
ColumnName, ColumnIndex (starting at 0),
SourceMemberName (the fieldname in the dataset), and a
FormatColumnExpressions item. The latter consists of
a SourceExpression (from the DataSet) and a
ControlExpression (from the StringGrid).
Become
BLAISE PASCAL MAGAZINE
subscriber ...
Figure 7
SEPTEMBER 2011 BLAISE PASCAL MAGAZINE 18
COMPONENTS
DEVELOPERS
115
Figure 9
In order to work on the BindGridLinkStringGrid11, we can double-click on that line in the above
dialog. This will produce a dialog where we can directly edit the expressions for the Columns of the
StringGrid. Right now, only one column exists, for the Species No:
Figure 10
Right-click on the Columns node to add new columns, and then use the Object Inspector to set the ColumnName and
SourceMemberName (for example both to Category), and then use the LiveBindings Expressions dialog to select the
ColFormat, CellFormat and optionally CellParse nodes, right-click on any of them to add a new item, and edit them
using this Binding Expression Editor. For example, right-click on the ColFormat node for the new Category column, and add a
new ColFormat expression, this will produce the following display in the Binding Expression Editor:
116
COMPONENTS
DEVELOPERS
Figure 11
Right-click on the Columns node to add new columns, and then use the Object Inspector to set the ColumnName and
SourceMemberName (for example both to Category), and then use the LiveBindings Expressions dialog to select the
ColFormat, CellFormat and optionally CellParse nodes, right-click on any of them to add a new item, and edit them
using this Binding Expression Editor. For example, right-click on the ColFormat node for the new Category column, and add a
new ColFormat expression, this will produce the following display in the Binding Expression Editor:
Figure 12
COMPONENTS
DEVELOPERS
117
Figure 13
In a similar way we can add a new Expression item for the
CellFormat, set the Control Expression to Cells[1] and
the Source Expression to AsString. Leading to the following
total of five expressions for the two columns in the StringGrid:
Figure 15
Figure 14
When running this VCL Forms application with an active TClientDataSet, the result is very similar to a TDBGrid, only this
time we're actually seeing the data inside a regular TStringGrid.
I leave it as exercise for the reader to add the other fields and columns, which shouldn't be hard now. This will take a bit more
effort to produce using regular Delphi code (without using data-aware controls), and the usefulness of LiveBindings becomes
even more apparent if you realize that FireMonkey, the cross-platform GUI framework, does not have a concept of data-aware
controls, but only relies on LiveBindings to put any data in components, including datasets in edits or grids. In fact,
FireMonkey makes it even easier, as we'll see when we create a Mac OS X demo project
118
COMPONENTS
DEVELOPERS
Figure 17
If you click on OK now, then two new components
will be created and placed on the FMX form:
a TBindScopeDB and a TBindingList (that
we saw before in the VCL example). Also, the
TStringGrid is filled with the live data from
the TClientDataSet:
Figure 16
The TStringGrid has an Align
property that works in FireMonkey as
well. We get a bit more choices
however:
alBottom, alCenter,
alClient, alContents,
alFit, alFitLeft,
alFitRight, alHorizontal,
alHorzCenter, alLeft,
alMostBottom, alMostLeft,
alMostRight, alMostTop,
alNone (the default), alRight,
alScale, alTop,
alVertCenter,and
alVertical.
Figure 18
For this demo, I've selected alFitLeft.
Next, we can open up the LiveBindings property of the
TStringGrid, and notice that apart from a choice New
LiveBinding (that we saw in the VCL world), there is now also a
choice Link to DB DataSource. If we pick the latter, we get
a dialog where we can select the datasource (DataSource1).
COMPONENTS
DEVELOPERS
119
Figure 19
If you double-click on the DBLinkStringGrid11, you can see all the individual Columns
and Expressions that were generated automatically (and were generated by hand in the VCL example).
Figure 20
Finally, to illustrate the fact that LiveBindings are not only more important, but also better implemented for FireMonkey
compared to the VCL, if we add a new LiveBinding in a FireMonkey application, the list of available choices is far more extensive,
and includes a new category DB Links with TBindDBEditLink, TBindDBTextLink, TBindDBListLink,
TBindDBImageLink, TBindDBMemoLink, TBindDBCheckLink, and TBindDBGridLink (the latter was used in
our recent example).
120
COMPONENTS
DEVELOPERS
Figure 21
Figure 22
Figure 23
Summary
In this article, I've shown what LiveBinding is, how it works (using a number of simple to more complex demos) for both VCL and
FireMonkey applications. Since the latter does not include data-aware controls, the importance of LiveBindings for
FireMonkey is enormous. There may be enhancements and changes to the implementation of LiveBindings (and some extra
documentation is also welcome), but for now it certainly is a good start.
Bob Swart (Bob@eBob42.com)
Bob Swart Training & Consultancy www.bobswart.com
COMPONENTS
DEVELOPERS
121
SUMMER OFFER:
LAZARUS
COMPONENTS
DEVELOPERS
Pag 87
Become
David I
Marco
Cant