Sunteți pe pagina 1din 97

The Delphi IDE

As explained in the first chapter of this course, one of the ways to start Delphi is to
choose Programs | Borland Delphi 6 | Delphi 6 from the Windows Start menu.
When Delphi starts (it could even take one full minute to start - depending on your
hardware performance) you are presented with the IDE: the user interface where you
can design, compile and debug your Delphi projects.

Like most other development tools (and unlike other Windows applications), Delphi
IDE comprises a number of separate windows.
The menus, toolbars

The main window, positioned on the top of the screen, contains the main menu,
toolbar and Component palette. The title bar of the main window contains the name of
the current project (you'll see in some of the future chapters what exactly is a Delphi
project). The menu bar includes a dozen drop-down menus - we'll explain many of the
options in these menus later through this course. The toolbar provides a number of
shortcuts to most frequently used operations and commands - such as running a
project, or adding a new form to a project. To find out what particular button does,
point your mouse "over" the button and wait for the tooltip. As you can see from the
tooltip (for example, point to [Toggle Form/Unit]), many toolbuttons have keyboard
shortcuts ([F12]).
The menus and toolbars are freely customizable. I suggest you to leave the default
arrangement while working through the chapters of this course.
The Component Palette
You are probably familiar with the fact that any window in a standard Windows
application contains a number of different (visible or not to the end user) objects, like:
buttons, text boxes, radio buttons, check boxes etc. In Delphi programming
terminology such objects are called controls (or components). Components are the
building blocks of every Delphi application. To place a component on a window you
drag it from the component palette. Each component has specific attributes that enable
you to control your application at design and run time.
Depending on the version of Delphi (assumed Delphi 6 Personal through this course),
you start with more than 85 components at your disposal - you can even add more
components later (those that you create or from a third party component vendor).
The components on the Component Palette are grouped according to the function they
perform. Each page tab in the Component palette displays a group of icons
representing the components you can use to design your application interface. For
example, the Standard and Additional pages include controls such as an edit box, a
button or a scroll box.
To see all components on a particular page (for example on the Win32 page) you
simply click the tab name on the top of the palette. If a component palette lists more
components that can be displayed on a page an arrow will appear on a far right side of
the page allowing you to click it to scroll right. If a component palette has more tabs
(pages) that can be displayed, more tabs can be displayed by clicking on the arrow
buttons on the right-hand side.
Form1 window

Each time you start Delphi, a new project is created that consists of one *empty*
window. A typical Delphi application, in most cases, will contain more than one
window - those windows are referred to as forms. In our case this form has a name, it
is called Form1. This form can be renamed, resized and moved, it has a caption and
the three standard minimize, maximize and close buttons. As you can see a Delphi
form is a regular Windows window.
Unit1.pas - the Code Editor window
If the Form1 is the active window and you press [F12], the Code Editor window will
be placed on top. As you design user interface of your application, Delphi
automatically generates the underlying Object Pascal code. More lines will be added
to this window as you add your own code that drives your application. This window
displays code for the current form (Form1); the text is stored in a (so-called) unit -
Unit1. You can open multiple files in the Code Editor. Each file opens on a new page
of the Code editor, and each page is represented by a tab at the top of the window.
The Object Inspector
Each component and each form, has a set of properties - such as color, size, position,
caption - that can be modified in the Delphi IDE or in your code, and a collection of
events - such as a mouse click, keypress, or component activation - for which you can
specify some additional behavior. The Object Inspector displays the properties and
events (note the two tabs) for the selected component and allows you to change the
property value or select the response to some event.
For example, each form has a Caption (the text that appears on it's title bar). To
change the caption of Form1 first activate the form by clicking on it. In the Object
Inspector find the property Caption (in the left column), note that it has the 'Form1'
value (in the right column). To change the caption of the form simply type the new
text value, like 'My Form' (without the single quotes). When you press [Enter] the
caption of the form will change to My Form.
Note that some properties can be changed more simply, the position of the form on the
screen can be set by entering the value for the Left and Top properties - or the form
can be simply dragged to the desired location.
The Object TreeView
Above the Object Inspector you should see the Object TreeView window. For the
moment it's display is pretty simple. As you add components to the form, you'll see
that it displays a component's parent-child relationships in a tree diagram. One of the
great features of the Object TreeView is the ability to drag and drop components in
order to change a component container without losing connections with other
components.
The Object TreeView, Object Inspector and the Form Designer (the Form1 window)
work cooperatively. If you have an object on a form (we have not placed any yet) and
click it, its properties and events are displayed in the Object Inspector and the
component becomes focussed in the Object TreeView.
Closing Delphi
To stop working in the Delphi IDE, you can point to the File | Exit option in the main
menu. If you have made changes to the current project, you'll be asked if you want to
save the changes. Since this is the first time we've done some changes to the project in
this course, and we do not yet know how a project is saved and what are the options,
select [No].
Creating your first 'Hello World' Delphi Application
It's time to create a simple example in Delphi now. When you start Delphi, a default
project is created with one form. This default project automatically creates a blank
form, with its associated unit file and a project file, among others.
To get started, from the beginning, close anything that's open by choosing File | Close
All from the main menu.

Before you create your first Delphi project, you need to know what you want to
develop; a DLL, MDI application, SDI application a CLX application (for Linux) or
something else. To start without all the bells and whistles we'll create a standard SDI
Windows application. Simply point your mouse to File | New and select Application.
This creates a new project group with a single application in it.

The new project contains an empty form, a unit (associated with its form), and a
project file. As you develop and build your application, new files will be created and
added to the project. The project files are listed in the Project Manager window,
display it by selecting View | Project Manager from the main Delphi menu. With the
Project Manager, you can easily visualize how all your project files are related. If you
share files among different projects, using the Project Manager is recommended
because you can quickly and easily see the location of each file in the project.
Application vs. CLX Application
With some versions of Delphi (supposed Delphi 6 Professional or Enterprise), you can
build and develop cross platform applications that can be ported to Linux and
compiled with Kylix. To develop a CLX application, instead of standard Windows
application, you could pick CLX Application from the File | New menu. The Delphi
IDE is similar to one when you build Windows applications, except that the
Component palette changes dynamically to show the objects that are available for use
in Linux CLX applications.
Since this course is about Delphi for the Windows platform, we will be exploring
Delphi programming from that point of view. However, if you have Kylix and want to
join this course you are of course encouraged to do so. Even though, my intention at
this stage of this Course, is not to explain differences between CLX (Linux) and VCL
(Windows) development you should know that there are no reasons why you should
not join the course and just have in mind that when we talk about, let's say,
form1.DFM you think form1.XFM.
Hello Delphi
Now that we've created a project, we can begin work on our first application. This
first application will be pretty simple - we'll change the caption of the (main) form
once the application is executed. The change will be initiated from code - no user
interaction will be necessary.
To add the code that changes the caption of the form we need to *call* the Code
Editor window. If you have the Project Manager displayed on the screen, double click
the Form1. This will bring up the Form designer window to the front. Another way to
bring the Form1 to the front of the screen is to select Form1 from the Window menu.
Once Form1 is on top and active, double click it. This action has the following result:
the Code editor is positioned on the top of the screen and Delphi creates the skeleton
code for the new event handler.
Note: another way of achieving the same result is to activate Form1 in the Object
Inspector, select the Events tab and double click in the OnCreate column value.
As stated in the second chapter of this course, each form has a collection of events –
such as a mouse click, keypress, or component activation – for which you can specify
some additional behavior. In this case the event is called OnCreate. This event occurs
when the form is created.
The skeleton code looks like:
procedure TForm1.FormCreate(Sender: TObject);
begin
//this is where your code goes
end
For the moment do not get bothered with the meaning of the text you see.
Now alter the code so that it looks like:
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := 'Hello Delphi! ' + DateTimeToStr(Now);
end
Running a project for the first time
To see the results of this action, you need to (successfully) compile and run you
project. From the Run menu choose Run or press F9. The compiler will try to build
the project and execute your application. If the compiler encounters an error, it
displays an Error dialog box. When you choose OK to dismiss the dialog box, the
Code editor places the cursor on the line of code containing the error.
Note: if you want to see progress reports while your program compiles, you'll need to
check the "Show compiler progress" check box in the "Compiling and running"
section on the Preferences page of the Environment Options dialog box. Call this
dialog box by selecting Environment Options from the Tools menu.

If everything goes well (it should) your application is executed and you see a blank
form on the screen. Note several things. First, the form is blank - there are no dots that
make up the grid you see when designing the form look. Second, there is a new button
on the Windows Task Bar - when you point to it you'll see that it has the Project1
caption. Third, the caption of Delphi IDE is changed from "Delphi 6 - Project 1" to
"Delphi 6 - Project 1 [Running]". And fourth, most important for us, the caption of the
form is Hello Delphi ! + *date and time of the execution*.
There is not much you can do with this window, you can move it resize it and finally
close it. Every time you (compile and) run this project a form caption will say Hello
Delphi with the date and time of the execution.
Ok, I know this is not a lot, but be patient - this is your first project - it is not supposed
to do something meaningful. If you want a little more, here goes another simpe
example.
Saving the project
To properly get the job done, you should save the project, along with all its associated
files. To save the current form design and its code, select File | Save All from the main
menu bar. By default, Delphi opens the Projects folder. I suggest you to create a new
folder (inside the Projects folder) for your project. Let's call it "HelloExample". While
in the Save As dialog, open the newly created HelloExample folder and save the
following files:
. save Unit1 as MainUnit.pas
. save Project1 as HelloProject.dpr
Note 1: When you have saved the unit file, the corresponding form was saved as
MainUnit.dfm
Note 2: In the Code Editor window, Unit1 is now referred to as MainUnit.
Note 3: Since you have saved the project with the *new* name, if you run your
application now, the button on the Task Bar will say "HelloProject". Of course the
name of the application and the name of the project do not need to be the same, later
we will see how to change the name of a Delphi application.
Note, if you open up the HelloExample folder in the Windows Explorer, you should
find several files inside it. These are MainUnit.pas, MainUnit.dfm and several others.
The most important file inside this folder is the HelloProject.exe. This is your
applications executable file, if you double click it you'll execute it. If you want to
"install" your application on another machine this is the only file you need to copy.
Getting HELP from Delphi
Let's stop for the moment to explore ways to get help from Delphi in situations when
help is necessary. First of all, Delphi is supplied with extensive documentation. If you
do not have the printed manuals, those that came (as PDF) with the installation will
do. As stated in the first chapter of this course, the books include:
. Quick Start - a brief introduction to Delphi,
. Object Pascal Language Guide - a complete reference to the underlying Delphi
programming language, and
. Developers Guide - which covers advanced topics, from creating database
applications to creating your custom components.

Beside printed materials, Delphi holds a great deal of information in the Help system.
Even though you'll need to learn how to use it, it is really worth it - there are many
code examples to help you understand all the nuts and bolts of Object Pascal
programming. What's more, context-sensitive Help is available from nearly every
portion of the Code editor. To get context-sensitive Help from the Code editor
window simply place the cursor on the property, event, method, procedure or type for
which you want Help, then press F1.
Try it. Position the mouse cursor inside the word "Caption" in the Code Editor (the
word Caption you typed in the only example so far) and hit the F1 key.

Once you press the F1 key, a pop up window will ask you to specify more exactly
what you want to know. Here comes the hard part: how in the world you know what
topic to pick. The "problem" lies in the fact that, in Delphi, many components have
properties of the same name (and behavior). To get the help on Form Caption property
you need to pick TControl.Caption. Why TControl, when you are working with Form
not something called TControl? Well, for the moment this is hard to explain, let's just
say that Form derives from something called Control and that Control has a Caption
property. What you will find out is that in general, Caption is used for text that
appears as a window title.
But how will you know what to pick? There is a solution. Point to Object Inspector,
Properties page. Select the property you want to find out about and than press F1.
Some exercises for you...
Since this Course is an online course, there is much you can do to prepare for the next
chapter. At the end of each chapter I'll try to provide several tasks for you to get more
familiar with Delphi and the topics we discuss in the current chapter. Here are some
exercises for you:
0. Learn about the Name property of the Form object. Note that the Name property
should tell you what the form does.
1. Explore the Object Inspector and try to figure what properties relate to the Form
positioning on the screen (Left, Top, Width, Height, ...) and what happens when you
alter them at design time.
2. Try to change the color of the Form from the Object Inspector (Color property)
3. Learn about the BorderIcons and BorderStyle properties and how they relate to
visual representation of the Form at run time.
4. Find what exactly DateTimeToStr is used for.
5. Be sure not to miss the next chapter!
Creating your second Delphi Application
In the previous chapter we have created a simple Delphi application without going
into details of the Object Pascal language behind all Delphi projects. This time, our
task is to create a more complex application that involves several Delphi components
- again, the application will be pretty simple. The idea is to help you understand the
Delphi Pascal source and how components operate and communicate with their
properties, methods and events.
To start, run Delphi. As explained, when you run Delphi a new project (application) is
created with one blank form.
Placing Components on a Form
All forms of a Delphi application have one or more components. Components, or
objects, usually display information or allow the user to perform an action. For
example a Label is used to display static text, an Edit box is used to allow user to
input some data, a Button can be used to initiate actions.
Any combination of components can be placed on a form, you should remember that
Windows is even-driven, while your application is running a user can interact with
any component on a form, it is your task, as a programmer, to decide what happens
when a user clicks a button or changes a text in an Edit box.
As stated before, Delphi supplies a number of components for you to create complex
user interfaces. You can find all the components you can place on a form on the
Component palette. To place a component on a form, locate its icon on the Palette and
double-click it. This action places a component on the active form. Visual
representation of most components is set with their set of properties. When you first
place a component on a form, it is placed in a default position, with default width and
height. You can change the size and position later, by using the Object Inspector.
Note: to remove a component from a form, click on it and press the [Del] key. Later,
in this Course, I'll explain what happens to code (if some exists) associated with the
component.
Your second Delphi application
We can now start adding components to a form. Activate the only form in a project,
point to the Component palette and select the "Standard" tab. We will add three
standard Windows components and write some example code to see how components
work together.

Double click the following three components:


 TLabel : use this component when you want to add some text to a form that the
user can't edit.
 TEdit : standard Windows edit control. Edit controls are used to retrieve text that
users type.
 TButton : use this component to put a standard push button on a form.
Using drag-and-drop to rearrange the components to appear on a form similar to:
Note: even though at this stage of the Course, it is not important to create "nicely
looking" user interfaces, I strongly encourage you to always try to arrange
components on form so they create a user friendly interface. Here are some
suggestions and Standards of Screen Design.
Changing Component Properties
After you place components on a form, you can set their properties with the Object
Inspector. The properties are different for each type of component, some properties
apply to most components. Altering a component property, changes the way a
component behaves and appears in an application.
All the components have a property called "Name". The Name property is very
important; it specifies the name of the component as referenced in code. When you
first place a component on a form, Delphi will provide a default name for the
component: Label1, Edit1, Button1. I suggest you to give your components a
meaningful name before writing the code that refers to them. You can do this by
changing the value of the Name property in the Object Inspector. Here are some
suggestions on how to give names to components.
Note: with the last statement in mind, I'll do the opposite. In most cases, I'll leave all
the default component names through this Course - just as they appear when you
place them on a form.
To actually change a component property you first need to activate it - click it to
select it - small square handles appear at each corner and in the middle of each side.
Another way to select a component is to click its name in the drop down list that
appears at the top of the Object Inspector. This list lists all the components on the
active form along with their types in the following format: "Name Type".
When a component is selected, its properties (and events) are displayed in the Object
Inspector. To change the component property click on a property name in the Object
Inspector; then either type a new value or select from the drop-down list.
For example, change the Caption property for Button1 (I'll refer components by their
names) to 'Hello...' (of course without the single quotation marks)

Components have different kinds of properties; some can store a boolean value (True
or False), like Enabled. To change a boolean property double click the property value
to toggle between the states. Some properties can hold a number (e.g. Width or Left),

a string (e.g. Caption or Text) or even a set of "simple valued" properties. When a
property has an associated editor, to set complex values, an ellipsis button appears
near the property name. For example if you click the ellipsis of the Font property a
Font property dialog box will appear.
Now, change the Caption (the static text the label displays on the form) of Label1 to
'Your name please:'. Change the Text property (text displayed in the edit box - this text
will be changeable at run time) of Edit1 to 'Zarko Gajic' (this is my name, write your
name).
Writing Code - Events and Event Handlers
To really enable components to do something meaningful you have to write some
action-specific code for each component you want to react on user input. Remember:
components are building block of any Delphi form, the code behind each component
ensures a component will react on an action.
Each Delphi component, beside its properties, has a set of events. Windows as even-
led environment requires the programmer to decide how a program will (if it will)
react on user actions. You need to understand that Windows is a message-based
operating system. System messages are handled by a message handler that translates
the message to Delphi event handlers. For instance, when a user clicks a button on a
form, Windows sends a message to the application and the application reacts to this
new event. If the OnClick event for a button is specified it gets executed.
The code to respond to events is contained in Delphi event procedures (event
handlers). All components have a set of events that they can react on. For example, all
clickable components have an OnClick event that gets fired if a user clicks a
component with a mouse. All such components have an event for getting and loosing
the focus, too. However if you do not specify the code for OnEnter and OnExit
(OnEnter - got focus; OnExit - lost focus) the event will be ignored by your
application.
To see a list of events a component can react on, select a component and in the Object
Inspector activate the Events tab. To really create an event handling procedure, decide
on what event you want your component to react, and double click the event name.
For example, select the Button1 component, and double click the OnClick event
name. Delphi will bring the Code Editor to the top of the screen and the skeleton code
for the OnClick event will be created.
procedure TForm1.Button1Click(Sender: TObject);
begin
//this is where your code goes
end
Note: For the moment there is no need to understand what all the words in the above
code stand for. Just follow along, we'll explain all that in the following chapters.
As you will understand more clearly through this course, a procedure must have a
unique name within the form. The above procedure, Delphi component event-driven
procedure, is named for you. The name consists of: the name of the form (prefixed
with T) "TForm", a full stop ".", the component name "Button1", and the event name
"Click". For any component there is a set of events that you could create event
handlers for. Just creating an event handler does not guarantee your application will
do something on the event - you must write some event handling code in the body of
the procedure.

A few words on Delphi (Object) Pascal


The code you write inside event procedures is Pascal code. Object Pascal or Delphi
Pascal (as I will mostly call it), a set of object-oriented extensions to standard Pascal,
is the language of Delphi. Delphi Pascal enables you to take advantage of object-
oriented programming to its fullest. It can be said that Delphi Pascal is to Pascal what
C++ is to C. As Delphi was being developed, new language behavior and keywords
were added to deal with the component model. In general, Delphi Pascal is a high-
level, compiled, strongly typed language that supports structured and object-oriented
design.
We'll now write some code for the OnClick event handler of Button1. Alter the above
procedure body to:
procedure TForm1.Button1Click(Sender: TObject);
var s: string;
begin
s := 'Hello ' + Edit1.Text + ' Delphi welcomes you!';
ShowMessage(s);
end;
A few words on Delphi Code completion
When you reach to the second line and write "Edit1." wait a little, Delphi will display
a list box with all the properties of the edit box you can pick. In general, it lists valid
elements that you can select from and add to your code. Here's more info on Delphi
Code Insight tools.
Now, hit F9 to compile and run your project. When the program starts, click the
Button1 ('Hello...'). A message box will pop up saying 'Hello Zarko Gajic, Delphi
welcomes you!'. Change the text in the Edit box and hit the Button again...
What follows is a simple explanation of the code that runs this application. Let's see.
 The first line under the procedure name, var s: string;, declares a string
typevariable. Variables in Delphi Pascal hold information (values). Variables have to
be declared before they can be used. We do this after the var keyword.
 The first line under the begin keyword, s := 'Hello ' + Edit1.Text + ' Delphi
welcomes you!'; sets a value for the variable s. This assignment involves reading a
value of the Text property for the Edit component. If you ask Delphi about the Text
property of an Edit component, you'll find out that it holds the text string that is
displayed in the edit box. That text is of the TCaption type, actually the string type.
 The last statement, before the end keyword, ShowMessage(s);, is the one that calls
the message dialog and sends it the value of variable s - this results in a pop up box
your see above.
That's it. Again, not too smart, not too hard but serves the purpose. By now you
should know how to place components on a form, set their properties and even do a
small do-something-funny Delphi application. Be sure to visit all the links in the
above paragraph.
Some exercises for you...
Since this Course is an online course, there is much you can do to prepare for the next
chapter. At the end of each chapter I'll try to provide several tasks for you to get more
familiar with Delphi and the topics we discuss in the current chapter. Here are some
exercises for you, after you finish reading this chapter:
1. Play with the Color property of the Form object
2. Use the Font property Editor to alter the font of the TLabel component
3. Find out about the PasswordChar property of the TEdit component to create a
simple password dialog form
4. Try adding a code for the OnCreate event of a form to make a form appear centered
on a screen. Also, make yourself familiar with the Position property of the TForm
object.
5. Be sure not to miss the next chapter!
Understanding the unit source
In the previous chapter you have created your second simple Delphi application
without going into details about the Delphi Pascal keywords that appear in each
Delphi unit. This time, our task is to take a look at exactly what each keyword means
by examining each line of the forms unit source code.
As you know already by now, forms are visible building blocks of all (well, at least
99%) Delphi projects. Each form in a Delphi project has an associated unit. The unit
contains the source code for any event handlers attached to the events of the form or
the components it contains.
The best way to describe the unit code is to take a look at the source. For the moment
reefer to the example in the last chapter, especially the unit source. After we have
placed a Label, an Edit box and a Button, and added an OnClick event handling
procedure for the button, the source code looked like:
01: unit Unit1;

02: interface

03: uses
03: Windows, Messages, SysUtils, Variants, Classes,
03: Graphics, Controls, Forms, Dialogs, StdCtrls;

04: type
05: TForm1 = class(TForm)
06: Edit1: TEdit;
07: Button1: TButton;
08: Label1: TLabel;
09: procedure Button1Click(Sender: TObject);
10: private
11: { Private declarations }
12: public
13: { Public declarations }
14: end;

15: var
16: Form1: TForm1;

17: implementation
18: {$R *.dfm}

19: procedure TForm1.Button1Click(Sender: TObject);


20: var s: string;
21: begin
22: s := 'Hello ' + Edit1.Text + ' Delphi welcomes you!';
23: ShowMessage(s);
24: end;

25: end.
We'll now explore and try to figure what each line stands for. First, a note: Delphi
units follow a predefined format. For example, the UNIT keyword must be the first
line in the source, followed by INTERFACE... This strict format is "predefined" by
Delphi, meaning that when you add an empty form to a project, its associated unit
source is already created with special keywords and type declaration. When you add a
component on a form and write an event handler for it, Delphi will place the
corresponding code at the exact location in the unit file.
Note another thing: in the above code, black lines appear in the forms unit source the
first time you add a form to a project. Lines colored in green were added by Delphi
when you have placed those three components on a form. Lines in red were added by
you in the previous chapter. Whenever you create a new form, Delphi creates the
corresponding unit file with the skeleton code marked black.
The rest of the article will discuss parts of the unit source. Several new words like
class, object and similar will be mentioned, do not get frightened if you do not
understand what they mean, let's just say that such words are a part of Delphi object
oriented programming, we will discuss them in the following chapters more clearly.
The UNIT keyword
A unit file begins with a unit heading, which is followed by the interface,
implementation, initialization, and finalization sections. The initialization and
finalization sections are optional.
The unit heading starts with a word unit (line 01), followed by a unit (file) name. The
unit name (Unit1 in the above source) must match the unit file name on a disk. In a
single project all unit names must be unique. You should change the unit's name only
by using the File-Save As command from the Delphi IDE main menu. Of course, it is
completely up to you to decide how will you name your units. In most cases you'll
want your units to have the name similar to the name of the form to which they are
linked, like 'MainUnit' for Main form (form with a Name property set to 'Main'). Be
sure to give name to units in the early stage of a form design development.
The INTERFACE section
The interface section of a unit starts with the word interface (line 02) and continues
until the word implementation (line 17). This section is used to declare
any publicsections of code that appear in a unit. The entire contents of the interface
section, including type, variable and procedure declarations, are visible to any other
unit which uses this unit. When any other part of the program looks at a unit, all it
sees is the interface section. Everything else is hidden, internal to the unit, part of the
implementation. You could say that the interface section contains a list of items in the
unit that other units can use.
In most cases the interface section will define several "subsections", you can see that
the code for unit1.pas has a uses clause, a type section, and a variable declaration
section.
The INTERFACE USES section
If the interface section includes a uses clause, it must appear immediately after the
word interface. A uses clause (line 03) lists units used by the unit. In most cases, all
necessary units are placed in the interface uses clause when Delphi compiler generates
and maintains a units source. The Windows, Messages, SysUtils, etc are all standard
Delphi units, required by a program.
As you drop components on a form, the necessary units will be added automatically to
the uses clause. For example, if you add a TOpenDialog component on your form
(Dialogs page on the component palette), the Dialogs unit will appear in the uses
clause because it contains the logic for the TOpenDialog component (and other
Dialog components).
In some situations, you'll need to manually add units to interface uses clause. Suppose
you are to use the TRegistry object, designed to access the Windows Registry. You
cannot drop the TRegistry component on a form, since it does not appear on the
component palette - you must manually add the word Registry to the uses list.
The INTERFACE TYPE section
Another part of the interface section is the type section. The form type declaration (or
form class declaration) section introduces the form as a class. The code from line 04
to 14 declares the existence and structure of a class called TForm1.
A few words on classes and objects
I'm aware that this is not the place to explain OOP in Delphi, but I sense that
something must be stated. The basics of object oriented programming in Delphi will
be discussed in the next chapter of this course, however some words must be
explained now.
A class, or class type, defines a structure consisting of fields, methods, and properties.
Instances of a class type are called objects.
For example, in real world, a class PROGRAMMER can have properties like:
Years_Of_Experience and Projects_Developed. It can expose methods like:
Write_Program and Talk_To_Users. A class is something that does not truly exists. An
object: DELPHI PROGRAMMER is a specific instance of a class.
The TForm1 is a class inherited from TForm (line 05).
Each component dropped on a form becomes a field (or variable) of the TForm1 class
(lines 06 through 08). For example, Edit1 is a variable of a TEdit type, which you see
on the screen when you run the program. When you need to read a value from this
particular edit box you use the Edit1 variable, like in 's := Edit1.Text'.
Each event handling procedure for a form events or events for components dropped
on a form (form fields) will have its declaration (line 09) in the interface type part.
For an explanation on private and public parts (lines 10 through 14) of a class
declaration, please refer to Hiding Data, a part of the Creating Custom Delphi
Components - Inside and Out article.
The INTERFACE VAR section
This part (line 15,16) of the interface section is used to declare (create) a Form1
object as an instance of the TForm1 class. If you have created your own data type
(with fields, properties and methods) as a part of this unit, you could create a variable
of that type in this part of the interface section.
The IMPLEMENTATION section
The implementation section is defined as everything between the implementation
word and either the initialization statement or the end of the file (as denoted by the
end. keyword).
The implementation is where you write code that performs actions. This section is
private to the unit, and can contain both declarations and code. The implementation
section of a unit can contain its own uses clause as well.
A few words on using another unit (form)
As you will see in the following chapters of this course, a (form) unit can use another
unit. Simply put, this means that one form can call another form. Suppose you have a
main form (form name: MainForm, unit name: MainFormUnit) in a project with an
'About...' button on it. What you want to do is to show an about box form (form name:
AboutForm, unit name: AboutFormUnit) when you click on this button. To be able to
do this the MainFormUnit must use the AboutFormUnit, the AboutFormUnit should
be placed in the implementation uses clause.
To actually call a method (procedure or function) from MainFormUnit that is declared
in the AboutFormUnit, you use the following syntax:
AboutFormUnit.SomeProcedureName(parameters)

Note that the call to a procedure SomeProcedureName consists of a unit name


(AboutFormUnit) followed by a period (.) and a procedure name. This fact is very
important. If in some stage of the development of your project you decide to save
AboutFormUnit under a different name - you will need to change the call to any
procedure inside that unit, since the name of the unit will no longer be
AboutFormUnit. This is the reason why you should give meaningful name to units in
the early stage of form (unit) development.
Anything that appears in a unit's implementation section that is not referenced in the
interface is private to that unit. This means that a procedure or function declared and
defined (implemented) in the implementation section cannot be called from another
unit unless its header is listed in that unit's interface.
The INITIALIZATION and FINALIZATION sections
These two sections are optional; they are not automatically generated when we create
a unit. If we want to initialize any data the unit uses, we can add an initialization code
to the initialization section of the unit. When an application uses a unit, the code
within the unit's initialization part is called before the any other application code runs.
If your unit needs to perform any cleanup when the application terminates, such as
freeing any resources allocated in the initialization part; you can add a finalization
section to your unit. The finalization section comes after the initialization section, but
before the final end.
Other units of a Delphi project
Now, when you have learned about the structure of a Delphi form unit, it's time to see
what other units can appear in a project. Every Delphi program has at least two main
parts. The first is a project file such as Project1.dpr. You can see the structure of the
project file by pointing you mouse to Project|View Source from the Delphi main
menu. The second part is one (or more) units attached to forms - the structure of such
a unit is discussed through this article. However, units don't have to be associated with
forms. A Code Unit contains code that is called from other units in the project. When
you start building libraries of useful routines, you will probably store them in a code
unit. To add a new code unit to Delphi application choose File-New ... Unit. The
structure of a code unit is similar to form unit, it only lacks the type declaration for the
"linked" TForm class.
Looking in the past: Code Explorer

In the second chapter of this course you have learned the main parts of the Delphi
IDE. When we were discussing the Code Editor window, one part of that window was
left unexplained - the Code Explorer. By default, the Code Explorer is docked to the
left of the Code editor.
The Code Explorer makes it easy to navigate through your unit source. The Code
Explorer contains a tree diagram that shows all the types, classes, properties, methods,
global variables, and global routines defined in your unit. It also shows the other units
listed in the uses clause.
I use the Code explorer to quickly jump to a procedure or type declaration by simply
double-clicking on an item name, like Button1Click.
Delphi Language: tutorials
Delphi language, a set of object-oriented extensions to standard Pascal, is the
language of Delphi. Delphi Pascal is a high-level, compiled, strongly typed language
that supports structured and object-oriented design. Its benefits include easy-to-read
code, quick compilation, and the use of multiple unit files for modular programming.
Here's a list of tutorials, an introduction to Delphi Pascal, that will help you learn
Delphi Pascal. Each tutorial will help you to understand a particular feature of Delphi
Pascal language, with practical and easy to understand code snippets.
Variable Scope
Object Pascal Variable Scope
As mentioned in some of the previous articlesunderstanding Object
Pascal variable scope is one of key elements in building applications with
Delphi/Object Pascal.
Scope of Variables and Constants
The term scope refers to the availability of avariable or constant declared (or used) in
one part of a program to other parts of a program.
Unless we specify otherwise, changing the value of a variable named, let's say,
SomeNumber in one procedure (function) will not affect another variable with the
same name in another procedure (function).
Since Delphi requires us to declare variables, it's a lot harder to fall into the trap
caused by side effects accidentally. As we know by now, every variable used in some
procedure has to be declared in the var section of the event handler.
In general, we declare a variable where we want to use it. For example, if we want to
use a variable in an event handler, we declare the variable within the event handler.
Local Scope (+ variable declaration and initialization)
Most variables have local scope, which means that the variable is visible only within
the code block in which it is declared (usually: Event Handler for some method). In
particular, an event handler will not normally have access to the value of a variable in
another event handler.
If we want to be sure a variable is local within an event handler, we have to declare it
in the var section inside the event handler. Since we must declare a variable before we
can use it, if we can use a variable without declaring it locally, we know that there is a
variable with greater scope with the same name somewhere around project.
Let us look at the first example:
1. Start Delphi, this will give us (by default) new application with one blank form.
2. Double click somewhere on the form (to create OnCreate event handler)
3. Write down this code:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(FloatToStr(SomeNumber));
end;

4. If you try to run your project now, you will be prompted with: "Undeclared
Identifier: 'SomeNumber'" error. This means that we haven't declared SomeNumber
variable in our project (note: entire project, not FormCreate event handler).
5. To declare SomeNumber variable as double type change your code to:
procedure TForm1.FormCreate(Sender: TObject);
var SomeNumber: double;
begin
ShowMessage(FloatToStr(SomeNumber));
end;

6. Run your project, message box will appear with some strange (value of the memory
region where variable is stored) number. Delphi will also give us "Variable
'SomeNumber' might not have been initialized" warning. This means that, before
using declared variable, it is a good practice to initialize it (just to be sure). For that
purpose add this line of code before ShowMessage...
SomeNumber := 123.45;

7. Now, when you run your project message box will display 123,45 (no errors, no
warnings).
Finally, we can see why local variables are called local...
8.Add one TButton component to form and double-click it to create Buttons OnClick
event handler. Add the following code (so that OnClick looks like):
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(FloatToStr(SomeNumber));
end;

Again we have "Undeclared Identifier: 'SomeNumber'" error. This is what local


variables are all about: even if we have declared (and initialized) SomeNumber
variable in OnCreate event handler of the form, SomeNumber is not accessible in the
OnClick handler of TButtton. We simply cannot use SomeNumber (with 123.45
value) in the OnClick (at least for now). This means that SomeNumber from
OnCreate and SomeNumber from OnClick are two different variables (that can, of
course, hold different values)
9. Don't close this project, jet. We will need it again...
10. Add the following line before ShowMessage in the OnClick event handler (we
will need it later, don't worry about this for now)
SomeNumber:=555.55;
Sharing variables across procedures (event handlers)
Occasionally we will want to share the values of variables (and constants) across
event handlers or across units. For example, if an application is designed to perform a
calculation involving one SomeNumber at a time, that SomeNumber should be
available to all procedures in a unit.
Depending on where we declare a variable, the variable can be thought of as a
true global variable accessible by any other code in the application, or a unit-level
variable accessible by any code in the unit.
Unit level variables - unit level scope
We put the declaration statements for unit-level variables in a var section in the
unit'simplementation section. Unit-level constants are declared in a const section.
Let's look at the second example:
0. We will be modifying our first example (be sure to have it)
1. Add declaration of SomeNumber, so that implementation code of the unit looks
like:
...
implementation
{$R *.DFM}
var
SomeNumber: Double;
...
2. Run your program. As you can see, we don't have "Undeclared Identifier:
'SomeNumber'"error in OnClick handler of the TButton. When program starts
message box will appear with 123.45 value. When you click on the Button1 message
box will display 555.55; that's why we need step 10 in the first example - we
have initialized SomeNumber to 555.55 in the OnClick event of the Button1.
What's this? We have two SomeNumber variables in our project and they both hold
different values.
Obviously, we have to be careful when assigning values to unit-level variables.
Although we can use the same variable (or constant) name for both local and unit-
level variables, this is not a good idea. Any var (or const) declaration contained in a
procedure takes precedence over global (unit-level) declarations. Duplicating the
names makes the global variable invisible to the procedure (Delphi doesn't tell us
whether a global variable has been defined with the same name as a local variable).
That is why SomeNumber holds the 123,45 value in the OnCreate event handler of
the form (we cannot use global variable SomeNumber in the OnCreate procedure)
Note 1: If you really have to use two variables with the same SomeNumber name (one
global and one local), you can access the global SomeNumber variable value in the
forms OnCreate procedure with the call to unit1. SomeNumber (unit1 is the name of
the unit with the global SomeNumber variable). That is, something like
unit1.SomeNumber:=444.44;
will change value of the global variable SomeNumber inside OnCreate event handler
of the form (remember that there is a SomeNumber variable local to this procedure
which will stay unchanged)
Note 2: As global SomeNumber is global to the unit we can access (more important:
change) its value from any other procedure inside this unit. However click to Button 1
will reset SomeNumber value to 555.55. Better way to initialize global variables is
inside initialization section of the unit.
Global variables - program level scope
If we want to create true global variables (or/and constants) in a project, we have to
place the declaration in the interface section of the unit. Variables declared in the
interface section will be visible (accessible) to any unit which uses that unit.
For example, to change SomeNumber variable value that is declared in Unit1 from
Unit2, use this statement:
Unit1.SomeNumber:=999.99;
Be sure to add Unit1 to the uses clause of Unit2.
Conclusion
That's it. I hope you have had the power to come to the end of this article. As we can
see, there is much to be stated about variable scope in Object Pascal. Of course, there
is more:static variables (or typed constants) are something we could name "constant
variables".
Typed constants
When Delphi invokes an event handler, the old values of local variables are wiped
out. What if we want to keep track of how many times a button has been clicked? We
could have the values persist by using a unit-level variable, but it is generally a good
idea to reserve unit-level variables only for sharing information. What we need are
usually called static variables or typed constants in Delphi.
Variable or constant?
Typed constants can be compared to initialized variables-variables whose values are
defined on entry to their block (usually event handler). Such a variable is initialized
only when the program starts running. After that, the value of a typed constant persists
between successive calls to their procedures.
Using typed constants is a very clean way of implementing automatically initialized
variables. To implement these variables without typed constants, we'll need to create
an initialization section that sets the value of each initialized variable.
Variable typed constants
Although we declare typed constants in the const section of a procedure, it is
important to remember that they are not constants. At any point in your application, if
you have access to the identifier for a typed constant you'll be able to modify its
value.
To see typed constants at work, put a button on a blank form, and assign the following
code to the OnClick event handler:
procedure TForm1.Button1Click(Sender: TObject) ;
const
clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
Notice that every time you click on the button, forms caption increments steadily.
Now try the following code:
procedure TForm1.Button1Click(Sender: TObject) ;
var
clicks : Integer;
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
We are now using uninitialized variable for the clicks counter. Notice that weird value
in the forms caption after you click on the button.
Constant typed constants
You have to agree that idea of modifiable constants sound a bit strange. In 32 bit
versions of Delphi Borland decided to discourage their use, but support them for
Delphi 1 legacy code.
We can enable or disable Assignable typed constants on the Compiler page of the
Project Options dialog box.
If you've disabled Assignable typed constants for a given project, when you attempt to
compile previous code Delphi will give you 'Left side cannot be assigned to' error
upon compilation. You can, however, create assignable typed constant by declaring:
{$J+}
const clicks : Integer = 1;
{$J-}
Therefore, the first example code looks like:
procedure TForm1.Button1Click(Sender: TObject) ;
const
{$J+}
clicks : Integer = 1; //not a true constant
{$J-}
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
Conclusion
It's up to you to decide whether you want typed constants to be assignable or not.
Important thing here is that besides ideal for counters, typed constants are ideal for
making components alternately visible or invisible, or we can use them for switching
between any Boolean properties. Typed constants can also be used inside TTimer's
event handler to keep track of how many times even has been triggered.
Loops
The loop is a common element in all programming languages. Object Pascal has three
control structures that execute blocks of code repeatedly: for, repeat ... until and while
... do.
The FOR loop
Suppose we need to repeat an operation a fixed number of times.
// show 1,2,3,4,5 message boxes
var j: integer;
begin
for j := 1 to 5 do
begin
ShowMessage('Box: '+IntToStr(j)) ;
end;
end;
The value of a control variable (j), which is really just a counter, determines how
many times a for statement runs. The keyword for sets up a counter. In the preceding
example, the starting value for the counter is set to 1. The ending value is set to 5.
When the for statement begins running the counter variable is set to the starting value.
Delphi than checks whether the value for the counter is less than the ending value. If
the value is greater, nothing is done (program execution jumps to the line of code
immediately following the for loop code block). If the starting value is less than the
ending value, the body of the loop is executed (here: the message box is displayed).
Finally, Delphi adds 1 to the counter and starts the process again.
Sometimes it is necessary to count backward. The downto keyword specifies that the
value of a counter should be decremented by one each time the loop executes (it is not
possible to specify an increment / decrement other than one). An example of a for
loop that counts backward.
var j: integer;
begin
for j := 5 downto 1 do
begin
ShowMessage('T minus ' + IntToStr(j) + 'seconds') ;
end;
ShowMessage('For sequence executed!') ;
end;
Note: it's important that you never change the value of the control variable in the
middle of the loop. Doing so will cause errors.
Nested FOR loops
Writing a for loop within another for loop (nesting loops) is very useful when you
want to fill / display data in a table or a grid.
var k,j: integer;
begin
//this double loop is executed 4x4=16 times
for k:= 1 to 4 do
for j:= 4 downto 1 do
ShowMessage('Box: '+ IntToStr(k)+ ',' + IntToStr(j)) ;
end;
The rule for nesting for-next loops is simple: the inner loop (j counter) must be
completed before the next statement for the outer loop is encountered (k counter). We
can have triply or quadruply nested loops, or even more.
Note: Generally, the begin and end keywords are not strictly required, as you can see.
If begin and end are not used, the statement immediately following the for statement
is considered the body of the loop.
The FOR-IN loop
If you have Delphi 2005 or any newer version, you can use the "new" for-element-in-
collection style iteration over containers. The following example demonstrates
iteration over string expressions: for each char in string check if the character is either
'a' or 'e' or 'i'.
const
s = 'About Delphi Programming';
var
c : char;
begin
for c in s do
begin
if c in ['a','e','i'] then
begin
// do something
end;
end;
end;
The WHILE and REPEAT loops
Sometimes we won't know exactly how many times a loop should cycle. What if we
want to repeat an operation until we reach a specific goal?
The most important difference between the while-do loop and the repeat-until loop is
that the code of the repeat statement is always executed at least once.
The general pattern when we write a repeat (and while) type of loop in Delphi is as
follows:
repeat
begin
statements;
end;
until condition = true
while condition = true do
begin
statements;
end;
Here is the code to show 5 successive message boxes using repeat-until:
var
j: integer;
begin
j:=0;
repeat
begin
j := j + 1;
ShowMessage('Box:'+IntToStr(j)) ;
end;
until j > 5;
end;
As you can see, the repeat statement evaluates a condition at the end of the loop
(therefore repeat loop is executed for sure at least once).
The while statement, on the other hand, evaluates a condition at the beginning of the
loop. Since the test is being done at the top, we will usually need to make sure that the
condition makes sense before the loop is processed, if this is not true the compiler
may decide to remove the loop from the code.
var j: integer;
begin
j:=0;
while j < 5 do
begin
j:=j+1;
ShowMessage('Box:'+IntToStr(j)) ;
end;
end;
Break and Continue
The Break and Continue procedures can be used to control the flow of repetitive
statements: The Break procedure causes the flow of control to exit a for, while, or
repeat statement and continue at the next statement following the loop
statement. Continueallows the flow of control to proceed to the next iteration of
repeating operation.
Decisions
if language = Delphi then
begin
Use(language)
end
else
Skip(language) ;

Branching
If you want to control the flow of code execution depending on what the program
has already done or what it has just encountered you need to use one of the two
Delphi Pascal branching statements: if statements and case statements.
The IF THEN ELSE statement
The if statement is used to test for a condition and then execute sections of code based
on whether that condition is True or False. The condition is described with a Boolean
expression, If the condition is True, the code flow branches one way. If the condition
is False, the flow branches in another direction. Let's see this behavior on an example:
var iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
if iNumber = 0 then
ShowMessage('Zero value encountered!') ;
end;
If the number (assigned to iNumber variable) is 0, the expression iNumber = 0
evaluates to True and the message is displayed; otherwise, nothing is displayed. If we
want more than one thing to happen when the tested condition is True, we can write
multiple statements in a begin ... end block.
var iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
if iNumber = 0 then
begin
ShowMessage('Zero value encountered!') ;
Exit; // exit from the current procedure
end;
//if iNumber is 0 the folowing
//code will never be executed
ShowMessage('Nobody likes 0, ha!') ;
end;
More often, we will want to process multiple statements if a condition is True or
False.
var iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
if iNumber < 0 then
begin
//statements ...
ShowMessage('Your number is negative!') ;
//statements ...
end
else
begin
//statements ...
ShowMessage('Your number is positive or zero!') ;
//statements ...
end;
end;
Note: Each statement in the begin..end block ends with a semicolon. We cannot have
a semicolon before or after the else keyword. The if-then-else statement, is a single
statement, therefore we cannot place a semicolon in the middle of it.
An if statement can be quite complex. The condition can be turned into a series of
conditions (using the and, or and not Boolean operators), or the if statement can nest a
second if statement.
var
iNumber : Integer;
begin
if iNumber = 0 then
begin
ShowMessage('Zero number not allowed!') ;
exit;
end
else
//no need to use begin-end here
if iNumber < 0 then
ShowMessage('Your number is negative!')
else
ShowMessage('Your number is positive!') ;
end;
Note: When you write nested if statements choose a consistent, clear indentation style.
This will help you and anyone else who reads your code see the logic of the if
statement and how the code flows when your application runs.
The CASE statement
Although, we can use the if statement for very complex (nested) condition testing, the
case statement is usually easier to read (debug!) and the code runs more quickly.
The case statement makes it clear that a program has reached a point with many
branches; multiple if-then statements do not.
var
iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
case iNumber of
0 : ShowMessage('Zero value') ;
1..10 : ShowMessage('Less than 11, greater than 0') ;
-1, -2, -3 : ShowMessage('Number is -1 or -2 or -3') ;
else
ShowMessage('I do not care') ;
end;
end;
What follows the case keyword is usually called the selector. The selector is a variable
or expression taken from either the char type or any integer type (an ordinal type).
String type are invalid!. However, the StringToCaseSelect custom function enables
you to use the Case statement with string type variables
As you can see, the individual case statements use a single constant, a group of
constants (separated by comma), or a range of constants (double dot separated). We
can even add an else keyword to take care of all the remaining cases at once.
Note 1: Only one case statement will be executed, we cannot have overlapping
conditions in the case statements.
Note 2: If you want to include more than one statement in the part following the colon
(:), place the begin and end keywords around the multiple statements.

Functions and Procedures


Have you ever found yourself writing the same code over and over to perform some
common task within event handlers? Yes! It's time for you to learn about programs
within a program. Let's call those mini programs subroutines.
Intro to subroutines
Subroutines are an important part of any programming language, and Delphi is no
exception. In Delphi, there are generally two types of subroutines: a function and
aprocedure. The usual difference between a function and a procedure is that a
function can return a value, and a procedure generally will not do so. A function
is normally called as a part of an expression.
Take a look at the following examples:
procedure SayHello(const sWhat:string) ;
begin
ShowMessage('Hello ' + sWhat) ;
end;

function YearsOld(const BirthYear:integer): integer;


var
Year, Month, Day : Word;
begin
DecodeDate(Date, Year, Month, Day) ;
Result := Year - BirthYear;
end;
Once subroutines have been defined, we can call them one or more times:
procedure TForm1.Button1Click(Sender: TObject) ;
begin
SayHello('Delphi User') ;
end;

procedure TForm1.Button2Click(Sender: TObject) ;


begin
SayHello('Zarko Gajic') ;
ShowMessage('You are ' + IntToStr(YearsOld(1973)) + ' years old!') ;
end;
Functions and Procedures
As we can see, both functions and procedures act like mini programs. In particular,
they can have their own type, constants and variable declarations inside them.
Take a closer look at a (miscellaneous) SomeCalc function:
function SomeCalc
(const sStr: string;
const iYear, iMonth: integer;
var iDay:integer): boolean;
begin
...
end;
Every procedure or function begins with a header that identifies the procedure or
function and lists the parameters the routine uses, if any. The parameters are listed
within parentheses. Each parameter has an identifying name and usually has a type. A
semicolon separates parameters in a parameter list from one another.
sStr, iYear and iMonth are called constant parameters. Constant parameters cannot be
changed by the function (or procedure). The iDay is passed as a var parameter, and
we can make changes to it, inside the subroutine.
Functions, since they return values, must have a return type declared at the end of the
header. The return value of a function is given by the (final) assignment to its name.
Since every function implicitly has a local variable Result of the same type as the
functions return value, assigning to Result has the same effect as assigning to the
name of the function.
Positioning and Calling Subroutines
Subroutines are always placed inside the implementation section of the unit. Such
subroutines can be called (used) by any event handler or subroutine in the same unit
that is defined after it.
Note: the uses clause of a unit tells you which units it can call. If we want a specific
subroutine in a Unit1 to be usable by the event handlers or subroutines in another unit
(say Unit2), we have to:
 Add Unit1 to the uses clause of Unit2
 Place a copy of the header of the subroutine in the interface section of the
Unit1.
This means that subroutines whose headers are given in the interface section
are global in scope.
When we call a function (or a procedure) inside its own unit, we use its name with
whatever parameters are needed. On other hand, if we call a global subroutine
(defined in some other unit, e.g. MyUnit) we use the name of the unit followed by a
period.
...
//SayHello procedure is defined inside this unit
SayHello('Delphi User') ;
//YearsOld function is defined inside MyUnit unit
Dummy := MyUnit.YearsOld(1973) ;
...
Note: functions or procedures can have their own subroutines embedded inside them.
An embedded subroutine is local to the container subroutine and cannot be used by
other parts of the program. Something like:
procedure TForm1.Button1Click(Sender: TObject) ;
function IsSmall(const sStr:string):boolean;
begin
//IsSmall returns True if sStr is in lowercase, False otherwise
Result:=LowerCase(sStr)=sStr;
end;
begin
//IsSmall can only be uses inside Button1 OnClick event
if IsSmall(Edit1.Text) then
ShowMessage('All small caps in Edit1.Text')
else
ShowMessage('Not all small caps in Edit1.Text') ;
end;

Routines in Delphi: Beyond the Basics


Functions and procedures are an important part of the Delphi language. Starting with
Delphi 4, Delphi allows us to work with functions and procedures that support default
parameters(making the parameters optional), and permits two or more routines to
have identical name, but operate as totally different routines.
Let's see how Overloading and default parameters can help you code better...
Overloading
Simply put overloading is declaring more than one routine with the same name.
Overloading allows us to have multiple routines that share the same name, but with
different number of parameters and their types.
As an example, let's consider the following two functions:
{Overloaded routines must be declared
with the overload directive}

function SumAsStr(a, b :integer): string;overload;


begin
Result := IntToStr(a + b) ;
end;

function SumAsStr(a, b : extended; Digits:integer): string; overload;


begin
Result := FloatToStrF(a + b, ffFixed, 18, Digits) ;
end;
These declarations create two functions, both called SumAsStr, that take different
number of parameters, that are of different types. When we call an overloaded routine,
the compiler must be able to tell which routine we want to call.
For example, SumAsStr(6, 3) calls the first SumAsStr function, because its arguments
are integer-valued.
Note that Delphi will help you pick the right implementation with the help of code
completion and code insight.
On the other hand, if we try to call the SumAsStr function as follows:
SomeString := SumAsStr(6.0,3.0)
we'll get the following error: "there is no overloaded version of 'SumAsStr' that can
be called with these arguments", meaning that we should also include the Digits
parameter used to specify the number of digits after the decimal point.
Note: there is only one rule when writing overloaded routines: an overloaded routine
must differ in at least one parameter type. The return type, instead, cannot be used
to distinguish among two routines.
Two units - one routine
Let's say we have one routine in unit A, and unit B uses unit A, but declares a routine
with the same name. The declaration in unit B does not need the overload directive:
we should use unit A's name to qualify calls to A's version of the routine from unit B.
Something like:
unit B;
...
uses A;
...
procedure RoutineName;
begin
Result := A.RoutineName;
end;
An alternative to using overloaded routines is to use default parameters, which usually
results in less code to write and maintain.
Default / Optional Parameters
In order to simplify some statements we can give a default value for the parameter of
a function or procedure, and we can call the routine with or without the parameter,
making it optional. To provide a default value, end the parameter declaration with the
= symbol followed by a constant expression.
For example, given the declaration
function SumAsStr (a,b : extended; Digits : integer = 2) : string;
the following function calls are equivalent.
SumAsStr(6.0, 3.0)
SumAsStr(6.0, 3.0, 2)
Note: parameters with default values must occur at the end of the parameter list, and
must be passed by value or as const. A reference (var) parameter cannot have a default
value.
When calling routines with more than one default parameter we cannot skip
parameters (like in VB):
function SkipDefParams(var A:string; B:integer=5, C:boolean=False):boolean;
...
//this call generates an error message
CantBe := SkipDefParams('delphi', , True) ;
Overloading with Default Parameters
When using both function or procedure overloading and default parameters, don't
introduce ambiguous routine declarations.
Consider the following declarations:
procedure DoIt(A:extended; B:integer = 0) ; overload;

procedure DoIt(A:extended) ; overload;


The call to DoIt procedure like DoIt(5.0) does not compile. Because of the default
parameter in the first procedure, this statement might call both procedures, because it
is impossible to tell which procedure is meant to be called.
Statements/Properties/Variables
The basic layout of a Pascal/Delphi program.
Starting with this article I'll try to present fundamentals of Object Pascal
Programming.
If you are new to Delphi, consider reading previous features for Delphi Beginners:
 Why Delphi? (what is Delphi and what can it do for you)
 Getting Started (an overview of Delphi programming and a simple application)
 Working With Components (introduction to Delphi VCL)
 Database Form Wizard (developing my own, first Delphi database application)
Visual programming is fun. Unfortunately, event handlers that you've seen, in those
articles, didn't do much. To fully understand and take advantage of Delphi, we must
become comfortable with the dialect of Object Pascal built into Delphi.
Statements or Long live the ";"
A statement in Delphi should be thought as a complete "sentence." Generally,
statements describe algorithmic actions that can be executed. Statements do things to
Delphi objects. These objects may be properties of components, numeric expressions
or some other more exotic gadgets.
Delphi distinguishes between simple statement and compound statements. A simple
statement is one executable line of code (not one line of code in Code Editor). A
compound statement is a block of simple statements surrounded
by begin/end keywords.
//simple statement
Form1.Caption := 'Ha! New caption ';
//compound statement
begin
Top := Top - 10;
Width := Width + 5;
end
As you can see, a semicolon (;) is always required to separate two simple statement.
Comments
Comments are lines of text in your code that are there for documentation purposes.
Comments do not take up any room in the compiled code. There are three ways to
indicate a comment:
// one-line comment
{this is also a comment - can be several lines}
(* this is another comment *)

Commenting out executable statements to help debug your programs is a common and
often necessary technique in programming languages. You'd be surprised how quickly
you forget what code you wrote is supposed to do.
Properties and Values or Long Live the ":="
To design the look of your application interface, you set the values of object
properties using the Object Inspector. This is referred to as making design time
settings. On the other hand, resetting properties via code (run time settings) is one of
the most common tasks in Delphi. Any property you can set at design time can also be
set at runtime by using code. There are also properties that can be accessed only at
runtime. These are known as runtime-only properties.
When we want to change a property setting (value) for a Delphi object we have to
place the object's name followed by a period (.) and the name of the property on the
left side of the colon equal (:=) combination - this is not some kind of a smiley.
ObjectName.Property := Value;

When you need to reset a large number of properties at one time, you probably would
not prefer to have to retype the name of the object each time. The with keyword lets
you do this by eliminating the need to type the object name over and over again.
Enough theory. Let's take a look at a real example:

procedure TForm1.Button1Click(Sender: TObject);


begin
Form1.Caption := 'Ha! New caption ';
Button1.Left := Button1.Left - 10;
with Button1 do begin
Top := Top - 10;
Width := Width + 5;
end
end;

Variables
Variables in Pascal hold informations (values). Variables have to be declared before
they can be used. We do this after the var keyword. The var keyword can be used in
several places in the code, such as at the beginning of the code of a function or
procedure, to declare variables local to the routine, or inside a unit to declare global
variables.
When declaring a variable, we must state its type. The type of a variable defines the
set of values the variable can have.
var
SomeValue : Integer;
NewAdress : string;
IsThisWorthy : boolean;
GimmeMoney : Currency;
Something : variant;
A, B, C : char;
Lookout : Pointer;
As you can see, we can declare more than one variable of the same type by placing a
comma between them. (SomeValue is variable of Integer type.)
Assigning values to variables
After you declare a variable, you can then use it to manipulate data in memory. Just as
for setting properties, Delphi uses the := sign for assigning values to variables. Like:
GimmeMoney := 323,23; //Curreny type variable
IsThisWorthy := True; //boolean type
NewAdress := 'My new home adress'; //string type

Delphi enforces strict rules on what kind of assignments we can make between
variable types. This means that something like
GimmeMoney := 'one thousand';
will give "Incompatible Types" error message. We can't assign string value to
currency type variable.
Another example (you should really try this one, it's fun):
procedure TForm1.Button2Click(Sender: TObject);
var
PosL, PosT : Integer;
TLCap: string;
begin
Randomize;
// assign value to PosL and PosT variables
PosL:=Random(Form1.ClientWidth-Button2.Width);
PosT:=Random(Form1.ClientHeight-Button2.Height);
// assign value to TLCap variable
TLCap:='Position: ';
TLCap:=TLCap + IntToStr(Button2.Left) + ' - ';
TLCap:=TLCap + IntToStr(Button2.Top);
//use variables to change object properties
Form1.Caption:=TLCap;
with Button2 do begin
Left:=PosL;
Top:=PosT;
end
end;
Constants
Constants (values that do not change during program execution) are declared using
the const keyword. To declare a constant you don't need to specify a data type, but
only assign an initial value.
const
Min = 30;
Max = 500;
Middle = (Max + Min) div 2;
ErrStr = 'Some errors occured';
MaxAmount = 123.45;

Obviously, line of code like


Middle:= 275;
will generate an error! ("Left side cannot be assigned to")
However, following line of code if quite usual/acceptable:
SomeValue := Middle + 200;
Final notes
My idea was to show you some basics of Delphi's built in programming language.
Object Pascal is far more complex and can give much more to Delphi developer. In
some future articles I'll be writing about Loops and Decisions, Procedures, Functions,
Arrays, Custom data types and so on...

String Types in Delphi


Understanding and managing string data types in Delphi's Object Pascal. Learn
about differences between Short, Long, Wide and null-terminated strings.
As like in any programming language, in Delphi, variables are placeholders used to
store values; they have names and data types. The data type of a variable determines
how the bits representing those values are stored in the computer's memory.
When we have a variable that will contain some array of characters, we can declare it
to be of type String.
Delphi provides a healthy assortment of string operators, functions and procedures.
Before assigning a String data type to a variable, we need to thorughly understand
Delphi's four string types.
Short String
Simply put, Short String is a counted array of (ANSII) characters, with up to 255
characters in the string. The first byte of this array stores the length of the string.
Since this was the main string type in Delphi 1 (16 bit Delphi), the only reason to use
Short String is for backward compatibility.
To create a ShortString type variable we use:
var s: ShortString;
s := 'Delphi Programming';
//S_Length := Ord(s[0]));
//which is the same as Length(s)

The s variable is a Short string variable capable of holding up to 256 characters, its
memory is a statically allocated 256 bytes. Since this is usually wastefull - unlikely
will your short string spread to the maximum length - second approach to using Short
Strings is using subtypes of ShortString, whose maximum length is anywhere from 0
to 255.
var ssmall: String[50];
ssmall := 'Short string, up to 50 characters';
This creates a variable called ssmall whose maximum length is 50 characters.
Note: When we assign a value to a Short String variable, the string is truncated if it
exceeds the maximum length for the type. When we pass short strings to some
Delphi's string manipulationg routine, they are converted to and from long string.
String / Long / Ansi
Delphi 2 brought to Object Pascal Long String type. Long string (in Delphi's help
AnsiString) represents a dynamically allocated string whose maximum length is
limited only by available memory. All 32-bit Delphi versions use long strings by
default. I recomend using long strings whenever you can.
var s: String;
s := 'The s string can be of any size...';
The s variable can hold from zero to any practical number of characters. The string
grows or shrinks as you assign new data to it.
We can use any string variable as an array of characters, the second character in s has
the index 2. The following code
s[2]:='T';
assigns T to the second character os the s variable. Now the few of the first characters
in slook like: TTe s str....
Don't be mislead, you can't use s[0] to see the length of the string, s is not ShortString.
Reference counting, copy-on-write
Since memory allocation is done by Delphi, we don't have to worry about garbage
collection. When working with Long (Ansi) Strings Delphi uses reference counting.
This way string copying is actually faster for long strings than for short strings.
Reference counting, by example:
var s1,s2: String;
s1 := 'first string';
s2 := s1;
When we create string s1 variable, and assign some value to it, Delphi allocates
enough memory for the string. When we copy s1 to s2, Delphi does not copy the
string value in memory, it ony increases the reference count and alters the s2 to point
to the same memory location as s1.
To minimize copying when we pass strings to routines, Delphi uses copy-on-write
techique. Suppose we are to change the value of the s2 string variable; Delphi copies
the first string to a new memory location, since the change should affect only s2, not
s1, and they are both pointing to the same memory location.
Wide String
Wide strings are also dynamically allocated and managed, but they don't use reference
counting or the copy-on-write semantics. Wide strings consist of 16-bit Unicode
characters.
About Unicode character sets
The ANSI character set used by Windows is a single-byte character set. Unicode
stores each character in the character set in 2 bytes instead of 1. Some national
languages use ideographic characters, which require more than the 256 characters
supported by ANSI. With 16-bit notation we can represent 65,536 different characters.
Indexing of multibyte strings is not reliable, since s[i] represents the ith byte (not
necessarily the i-th character) in s.
If you must use Wide characters, you should declare a string variable to be of the
WideString type and your character variable of the WideChar type. If you want to
examine a wide string one character at a time, be sure to test for multibite characters.
Delphi doesn't support automatic type conversions betwwen Ansi and Wide string
types.
var s : WideString;
c : WideChar;

s := 'Delphi_ Guide';
s[8] := 'T';
//s='Delphi_TGuide';
Null terminated
A null or zero terminated string is an array of characers, indexed by an integer
starting from zero. Since the array has no length indicator, Delphi uses the ASCII 0
(NULL; #0) character to mark the boundary of the string.
This means there is essentially no difference between a null-terminated string and an
array[0..NumberOfChars] of type Char, where the end of the string is marked by #0.
We use null-terminated strings in Delphi when calling Windows API functions. Object
Pascal lets us avoid messing arround with pointers to zero-based arrays when
handling null-terminated strings by using the PChar type. Think of a PChar as being a
pointer to a null-terminated string or to the array that represents one. For more info on
pointers, check:Pointers in Delphi.
For example, The GetDriveType API function determines whether a disk drive is a
removable, fixed, CD-ROM, RAM disk, or network drive. The following procedure
lists all the drives and their types on a users computer. Place one Button and one
Memo component on a form and asign an OnClick handler of a Button:
procedure TForm1.Button1Click(Sender: TObject);
var
Drive: Char;
DriveLetter: String[4];
begin
for Drive := 'A' to 'Z' do
begin
DriveLetter := Drive + ':\';
case GetDriveType(PChar(Drive + ':\')) of
DRIVE_REMOVABLE:
Memo1.Lines.Add(DriveLetter + ' Floppy Drive');
DRIVE_FIXED:
Memo1.Lines.Add(DriveLetter + ' Fixed Drive');
DRIVE_REMOTE:
Memo1.Lines.Add(DriveLetter + ' Network Drive');
DRIVE_CDROM:
Memo1.Lines.Add(DriveLetter + ' CD-ROM Drive');
DRIVE_RAMDISK:
Memo1.Lines.Add(DriveLetter + ' RAM Disk');
end;
end;
end;
Mixing Delphi's strings
We can freely mix all four different kinds of strings, Delphi will give it's best to make
sence of what we are trying to do. The assignment s:=p, where s is a string variable
and p is a PChar expression, copies a null-terminated string into a long string.
Character types
In addition to four string data types, Delphi has three character types: Char, AnsiChar,
andWideChar. A string constant of length 1, such as 'T', can denote a character value.
The generic character type is Char, which is equivalent to AnsiChar. WideChar values
are 16-bit characters ordered according to the Unicode character set. The first 256
Unicode characters correspond to the ANSI characters.
Ordinal and Enumerated Data Types
Intro to Types
Delphi's programming language is an example of a strongly typed language. This
means that all variables must be of some type. A type is essentially a name for a kind
of data. When we declare a variable we must specify its type, which determines the
set of values the variable can hold and the operations that can be performed on it.
Many of Delphi's built-in data types, such as Integer or String, can be refined or
combined to create new data types. In this article we'll see how to create custom
ordinal data types in Delphi.
Ordinal types
The defining characteristics of ordinal data types are: they must consist of a finitive
number of elements and they must be ordered in some way.
The most common examples of ordinal daty types are all the Integer types as well as
Char and Boolean type. More preciselly, Object Pascal has twelve predefined ordinal
types: Integer, Shortint, Smallint, Longint, Byte, Word, Cardinal, Boolean, ByteBool,
WordBool, LongBool, and Char. There are also two other classes of user-defined
ordinal types: enumerated types and subrange types.
In any ordinal types, it must make sense to move backward or forward to the next
element. For example, real types are not ordinal because moving backward or forward
doesn't make sense: the question "What is the next real after 2.5?" is meaningless.
Since, by definition, each value except the first has a unique predecessor and each
value except the last has a unique successor, several predefined function are used
when working with ordinal types:
Function Effect
Ord(X) Gives the index of the lement
Pred(X) Goes to the element listed before X in the type
Succ(X) Goes to the element listed after X in the type
Dec(X;n) Moves n elements back (if n is omitted moves 1 element back)
Inc(X;n) Moves n elements forward (if n is omitted moves 1 element forward)
Low(X) Returns the lowest value in the range of the ordinal data type X.
High(X) Returns the highest value in the range of the ordinal data type X.
For example, High(Byte) returns 255 because the highest value of type Byte is 255,
and Succ(2) returns 3 because 3 is the successor of 2.
Note: If we try to use Succ when at the last element Delphi will generate a run-time
exception if the range checking is on.
Enumerated Data Types
The easiest way to create a new example of an ordinal type is simply to list a bunch of
elements in some order. The values have no inherent meaning, and their ordinality
follows the sequence in which the identifiers are listed. In other words, an
enumeration is a list of values.
type TWeekDays = (Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday, Sunday);
Once we define an enumerated data type, we can declare variables to be of that type:
var SomeDay : TWeekDays;
The primary purpose of an enumerated data type is to make clear what data your
program will manipulate. An enumerated type is really just a shorthand way of
assigning sequential values to constants. Given these declarations, Tuesday is a
constant of type TWeekDays.
Delphi allows us to work with the elements in an enumerated type using an index that
comes from the order that they were listed in. In the previous example: Monday in
theTWeekDays type declaration has the index 0, Tuesday has the index 1, and so on.
The functions listed in the table before let us, for example, use Succ(Friday) to "go to"
Saturday.
Now we can try something like:
for SomeDay := Monday to Sunday do
if SomeDay = Tuesday then
ShowMessage('Tuesday it is!');
The Delphi Visual Component Library uses enumerated types in many places. For
example, the position of a form is defined as follows:
TPosition = (poDesigned, poDefault, poDefaultPosOnly,
poDefaultSizeOnly, poScreenCenter);
We use Position (through the Object Inspector) to get or set the size and placement of
the form.
Subrange Types
Simply put, a subrange type represents a subset of the values in another ordinal type.
In general, we can define any subrange by starting with any ordinal type (including a
previously defined enumerated type) and using a double dot:
type TWorkDays = Monday .. Friday;
Here TWorkDays includes the values Monday, Tuesday, Wednesday, Thursday and
Friday.
That's all ... go enumerate :)

Arrays in Object Pascal


The concept of arrays in Delphi is simple: arrays allow us to refer to a series of
variables by the same name and to use a number (an index) to tell them apart. Arrays
have both upper and lower bounds, and the elements of the array are contiguous
within those bounds.
Elements of the array are values that are all of the same type (string, integer, record,
custom object).
In Delphi, there are two types of arrays: a fixed-size array which always remains the
same size - static array, and a dynamic array whose size can change at run-time.
Static Arrays
Suppose we are writing a program that lets a user enter some values (e.g. the number
of appointments) at the beginning of each day. We would choose to store the
information in a list. We could call this list Appointments, and each number might be
stored as Appointments[1], Appointments[2], an so on.
To use the list, we must first declare it. For example:
var Appointments : array[0..6] of Integer;
declares a variable called Appointments that holds an one-dimensional array (vector)
of 7 integer values. Given this declaration, Appointments[3] denotes the fourth integer
value in Appointments. The number in the brackets is called the index.
If we create a static array but don’t assign values to all its elements, the unused
elements contain random data; they are like uninitialized variables. The following
code can be used to set all elements in the Appointments array to 0.
for k := 0 to 6 do Appointments[k] := 0;
Sometimes we need to keep track of related information in an array. For example, to
keep track of each pixel on your computer screen, you need to refer to its X and Y
coordinates. This can be done using a multidimensional array to store the values.
With Delphi, we can declare arrays of multiple dimensions. For example, the
following statement declares a two-dimensional 7 by 24 array:
var DayHour : array[1..7, 1..24] of Real;
To compute the number of elements in a multidimensional array, multiply the number
of indexes. The DayHour variable, declared above, sets aside 168 (7*24) elements, in
7 rows and 24 columns. To retreive the value from cell in the third row and seventh
column we would use: DayHour[3,7] or DayHour[3][7]. The following code can be
used to set all elements in the DayHour array to 0.
for i := 1 to 7 do
for j := 1 to 24 do
DayHour[i,j] := 0;
Note: here's How to Declare and Initialize Constant Arrays
Dynamic Arrays
Sometimes you may not know exactly how large to make an array. You may want to
have the capability of changing the size of the array at run time.
A dynamic array declares its type, but not its size. The actual size of a dynamic array
can be changed at run time by the use of the SetLength procedure.
For example, the following variable declaration
var Students : array of string;
creates a one-dimensional dynamic array of strings. The declaration does not allocate
memory for Students. To create the array in memory, we call SetLength procedure.
For example, given the declaration above,
SetLength(Students, 14) ;
allocates an array of 14 strings, indexed 0 to 13. Dynamic arrays are always integer-
indexed, always starting from 0 to one less than their size in elements.
To create a two-dimensional dynamic array, use the following code:
var Matrix: array of array of Double;
begin
SetLength(Matrix, 10, 20)
end;
which allocates space for a two-dimensional, 10 x 20, array of Double floating-point
values.
Note: To remove a dynamic array's memory space we assign nil to the array variable,
like:
Matrix := nil;
Very often, your program doesn't know at compile time how many elements will be
needed, that number will not be known until runtime. With dynamic arrays you can
allocate only as much storage as is required at a given time. In other words, the size of
dynamic arrays can be changed at run time, which is one of the key advantages to
dynamic arrays. The next code creates an array of integer values and then calls the
Copy function to resize the array.
var
Vector: array of Integer;
k : integer;
begin
SetLength(Vector, 10) ;
for k := Low(Vector) to High(Vector) do
Vector[k] := i*10;
...
//now we need more space
SetLength(Vector, 20) ;
//here, Vector array can hold up to 20 elements
//(it already has 10 of them)
end;
Note 1: SetLength function creates a larger (or smaller) array, and copies the existing
values to the new array.
Note 2: The Low and High functions ensure you access every array element without
looking back in your code for the correct lower and upper index values.
Arrays in Delphi allow us to refer to a series of variables by the same name and to use
a number (an index) to tell them apart.
Here's an example integer array that can hold up to 7 (integer) values. Note: this is a
fixed-size static Delphi array declaration.
var DayVisitors : array[0..6] of Integer;
Arrays as Function Return Types
In Delphi, functions are routines that return a value.
When you want a function to return an array type variable, you might be tempted to
use the next declaration:
function GetWeekTotal(weekIndex : integer) :array[0..6] of integer;
begin
//this will NOT compile
end;
When you try to compile this code, you'll get the next compile-time error: [Pascal
Error] E2029 Identifier expected but 'ARRAY' found.
Obviously, when you declare functions that will return array value, you cannot
include index type specifiers return declaration.
In order to allow a function to return an array value, you first need to create a
custom array type, then use it as a return function type:
//this WILL compile
type
TDayVisitors = array[0..6] of integer;

...

function GetWeekTotal(weekIndex : integer) : TDayVisitors;


begin
//do some calculation for the provided "week"
end;
Arrays as Method/Routine Properties
Similar to using arrays as function return types, when you declare routines that take
array parameters, you cannot include index type specifiers in the parameter
declarations.
type
TDayVisitors = array[0..6] of integer;

...

procedure DisplayWeekTotal(weekVisitors : TDayVisitors) ;


begin
//display some info for the provided "week"
end;

Records in Delphi
Suppose we want to create three one-dimensional arrays for 50 members in our
programming community. The first array is for names, the second for e-mails, and the
third for number of uploads (components or applications) to our community.
Each array (list) would have matching indexes and plenty of code to maintain all three
lists in parallel. Of course, we could try with one three-dimensional array, but what
about it's type? We need string for names and e-mails, but an integer for the number of
uploads.
The way to work with such a data structure is to use Delphi's record structure.
TMember = record ...
For example, the following declaration creates a record type called TMember, the one
we could use in our case.
type
TMember = record
Name : string;
eMail : string;
Posts : Cardinal;
end;
Essentially, a record data structure can mix any of Delphi's built in
types including any types you have created. Record types define fixed collections of
items of different types. Each item, or field, is like a variable, consisting of a name
and a type.
TMember type contains three fields: a string value called Name (to hold the name of a
member), a value of a string type called eMail (for one e-mail), and an integer
(Cardinal) called Posts (to hold the number of submissions to our community).
Once we have set up the record type, we can declare a variable to be of type
TMember. TMember is now just as good variable type for variables as any of Delphi's
built in types like String or Integer. Note: the TMember type declaration, does not
allocate any memory for the Name, eMail, and Posts fields;
To actually create an instance of TMember record we have to declare a variable of
TMember type, as in the following code:
var DelphiGuide, AMember : TMember;
Now, when we have a record, we use a dot to isolate the fields of DelphiGuide:
DelphiGuide.Name := 'Zarko Gajic';
DelphiGuide.eMail := 'delphi.guide@about.com';
DelphiGuide.Posts := 15;
Note: the above piece of code could be rewritten with the use of with keyword:
with DelphiGuide do
begin
Name := 'Zarko Gajic';
eMail := 'delphi.guide@about.com';
Posts := 15; end;
We can now copy the values of DelphiGuide’s fields to AMember:
AMember := DelphiGuide;
Record Scope and visibility
Record type declared within the declaration of a form (implementation section),
function, or procedure has a scope limited to the block in which it is declared. If the
record is declared in the interface section of a unit it has a scope that includes any
other units or programs that use the unit where the declaration occurs.
To learn more about Delphi's variable scope go the Variable Scope article.
An Array of Records
Since TMember acts like any other Object Pascal type, we can declare an array of
record variables:
var DPMembers : array[1..50] of TMember;
To access the fifth member we use:
with DPMembers[5] do begin
Name := 'First name Last';
eMail := 'FirstLast@domain.com'
Posts := 0;
end;
Or, to display information (e-mail, for example) about every member we could use:
var k: cardinal;

for k:= 1 to 50 do
ShowMessage(DPMembers[k].eMail) ;
Note: Here's how to declare and initialize a constant array of records in Delphi
Records as Record fields
Since a record type is legitimate as any other Delphi type, we can have a field of a
record be a record itself. For example, we could create ExpandedMember to keep
track of what the member is submitting along with the member information:
type
TExpandedMember = record
SubmitType : string;
Member : TMember;
end;
Filling out all the information needed for a single record is now somehow harder.
More periods (dots) are required to access the fields of TExpandedMember:
var SubTypeMember :TExpandedMember;

SubTypeMember.SubmitType := 'VCL';
SubTypeMember.Member.Name := 'vcl Programmer';
SubTypeMember.Member.eMail := 'vcl@about.com';
SubTypeMember.Member.Name := 555;
Record with "unknown" fields
A record type can have a variant part (I don't mean Variant type variable). Variant
records are used, for example, when we want to create a record type that has fields for
different kinds of data, but we know that we will never need to use all of the fields in
a single record instance. To learn more about Variant parts in Records take a look at
Delphi's help files. The use of a variant record type is not type-safe and is not a
recommended programming practice, particularly for beginners.
Variant Records in Delphi
A record is a special kind of user-defined data type. A record is a container for a
mixture of related variables of diverse types, referred to as fields, collected into one
type. Records are sometimes called complex types, because they are made up of other
data types. Other data types by comparison, are often referred to as simple data types.
Essentially, a record data structure can mix any of Delphi's built in types including
any types you have created.
Record Types
I can hear beginners saying "I really don't have a use for them…" or "I will learn them
later when I am not so busy". Well, later is not always the best time to learn things,
especially when "later" may be crunch time when unwelcome bugs habitually creep
into applications!
Records are commonly used in Microsoft Windows API calls, where they are referred
to as "structures", which is C++ programming language terminology for a very similar
thing.
Suppose you are writing an application and you need to determine a form's original
state before minimizing or maximizing the form, or get/set the size a form can shrink
to or grow to. Some of this can be done with plain old Delphi code while other parts
need to be done using API calls. If you need to restrict form sizing then you are going
to have to use WM_GETMINMAXINFO from the API. As you might have guessed,
that uses a record. In Delphi Win32 Help you will find that the record used is defined
as:
typedef struct tagMINMAXINFO { // mmi
POINT ptReserved;
POINT ptMaxSize;
POINT ptMaxPosition;
POINT ptMinTrackSize;
POINT ptMaxTrackSize;
} MINMAXINFO;
The Delphi architects thoughtfully wrote the interface translation to handle this, but
you need to search though the Delphi source code to find the information. The
following record is defined in Messages.pas:
TWMGetMinMaxInfo = record
Msg: Cardinal;
Unused: Integer;
MinMaxInfo: PMinMaxInfo;
Result: Longint;
end;
The rest is found in the Windows unit:
type
{ Struct pointed to by WM_GETMINMAXINFO lParam }
PMinMaxInfo = ^TMinMaxInfo;
TMinMaxInfo = packed record
ptReserved: TPoint;
ptMaxSize: TPoint;
ptMaxPosition: TPoint;
ptMinTrackSize: TPoint;
ptMaxTrackSize: TPoint;
end;
Of course you could say that not much knowledge is needed for accomplishing the
task we set up, but what if Delphi didn't have code for the above? Obviously you
would have had to write it yourself, and without the proper knowledge it would be
impossible to code. At times you need many pieces of information about a form. One
of the API calls to acquire the information is called GetWindowPlacement, and to
change form stuff that Delphi does not directly handle you might need to call
SetWindowPlacement. Both require the use of this record:
typedef struct _WINDOWPLACEMENT { // wndpl
UINT length;
UINT flags;
UINT showCmd;
POINT ptMinPosition;
POINT ptMaxPosition;
RECT rcNormalPosition;
} WINDOWPLACEMENT;
Delphi has defined it, but if they had not, we would be translating it ourselves!
Tkg_WINDOWPLACEMENT = Record
Length : Integer;
Flags : Integer;
ShowCmd : Integer;
ptMinPosition : TPoint;
ptMaxPosition : TPoint;
rcNormalPosition : TRect;
end;
Variant records
This demonstrates the importance of knowing how to work with records for API calls.
While everyday cases are not obvious, they do arise. One is hinted at in Delphi Help,
with an example:
TPerson = record
FirstName : string[40];
LastName : string[40];
{ Fixed portion of record begins here }
BirthDate: TDate;
case
Citizen: Boolean of
{ variant portion of record begins here }
True: (BirthPlace: string[40]);
False:
(
Country: string[20];
EntryPort: string[20];
EntryDate: TDate;
ExitDate: TDate
);
end;
As you can see, record type can have a variant part, which looks like a case statement.
The first part of the declaration - up to the reserved word case - is the same as that of
astandard record type. The remainder of the declaration - from case to the optional
final semicolon - is called the variant part.
The above variant record declaration has a section (which must follow the fixed
section) that can have multiple personalities. In the above example,
if Citizen equaled true then we would have:
TPerson = record
FirstName, LastName: string[40];
BirthDate: TDate;
BirthPlace: string[40];
end;
If Citizen was false:
TPerson = record
FirstName, LastName: string[40];
BirthDate: TDate;
Country: string[20];
EntryPort: string[20];
EntryDate: TDate;
ExitDate: TDate;
end;
Stacking Records into An Array
Now we have a understanding of records, let's step into another dimension and create
an array of records which allows you to store multiple records which can be returned
to a calling form. Place the following declaration into the interface section of a form.
TPerson = record
FirstName, LastName: string[40] ;
BirthDate: TDate ;
BirthPlace: string[40] ;
end;
Add a Memo control and a button to the form, enter the code below:
procedure TForm1.Button1Click(Sender: TObject);
var
MyPeople: Array[0..2] of TPerson ;
i:Integer ;
begin
Memo1.Clear ;
for i := 0 to 2 do
begin
MyPeople[i].FirstName := 'MyPeople[' + IntToStr(i) + '].FirstName' ;
MyPeople[i].LastName := 'MyPeople[' + IntToStr(i) + '].LastName';
MyPeople[i].BirthDate := Now;
MyPeople[i].BirthPlace := 'MyPeople[' + IntToStr(i) + '].BirthPlace';
end ;

for i := 0 to 2 do
begin
with Memo1.Lines do
begin
Add(MyPeople[i].FirstName + ' ' + MyPeople[i].LastName);
Add(DateToStr(MyPeople[i].BirthDate));
Add(MyPeople[i].BirthPlace);
Add('');
end;
end;
end;
Pressing the button populates the array of records with dummy information, then
displays the records in the memo control. This should give you a starting point to
working with record arrays. The main thing to remember about the example above is
that we created a record type, supplied the name of TPerson, then created a local
variable called MyPeople which is an array of type TPerson which can hold three (3)
rows of information.
Pointers in Delphi
Even though pointers are not so important in Delphi as the are in C or C++, pointers
are such a "basic" tool that almost anything having to do with programming must deal
with pointers in some fashion. For that reason, you will read that "a string is really
just a pointer" or that "an object is really just a pointer" or "event handler such as
OnClick is actually a pointer to a procedure".
Pointer as Data Type
Simply put, a pointer is a variable that holds the address of anything in memory.
To concrete this definition, keep in mind the following: everything used in an
application is stored somewhere in the computer's memory. Because a pointer holds
the address of another variable, it is said to point to that variable.
Most of the time pointers in Delphi point to a specific type:
var
iValue, j : integer;
pIntValue : ^integer;
begin
iValue := 2001;
pIntValue := @iValue;
...
j:= pIntValue^;
end;
The syntax to declare a pointer data type uses acaret (^). In the code above iValue is
an integer type variable and pIntValue is an integer type pointer. Since a pointer is
nothing more than an address in memory, we must assign to it the location (address)
of value stored in iValue integer variable. The @ operator returns the address of a
variable (or a function or procedure as will be seen later in this article). Equivalent to
the @ operator is the Addr function. Note that pIntValue's value is not 2001.
In the code above the pIntValue is a typed integer pointer. Good programming style is
to use typed pointers as much as you can. The Pointer data type is a generic pointer
type - represents a pointer to any data.
Note that when "^" appears after a pointer variable it dereferences the pointer; that is,
it returns the value stored at the memory address held by the pointer. In the code
above (after it) variable j has the same value as iValue. It might look like this has no
purpose when we can simply assign iValue to j, but this piece of code lies behind most
calls to Win API.
NILing pointers
Unassigned pointers are dangerous. Since pointers let us work directly with
computer's memory, if we try to (by mistake) write to a protected location in memory
we could get aaccess violation error. This is the reason why we should always
initialize a pointer to NIL.
NIL is a special constant that can be assigned to any pointer. When nil is assigned to a
pointer, the pointer doesn’t reference anything. Delphi presents, for example, an
emptydynamic array or a long string as a nil pointer.
Character Pointers
The fundamental types PAnsiChar and PWideChar represent pointers to AnsiChar and
WideChar values. The generic PChar represents a pointer to a Char variable. These
character pointers are used to manipulate null-terminated strings. Think of a PChar as
being a pointer to a null-terminated string or to the array that represents one. For more
on null-terminated strings go see: "String types in Delphi".
Pointers to Records
When we define a record or other data type, it's a common practice to also to define a
pointer to that type. This makes it easy to manipulate instances of the type without
copying large blocks of memory.
The ability to have pointers to records (and arrays) makes it much more easier to set
up complicated data structures as linked lists and trees.
type
pNextItem = ^TLinkedListItem
TLinkedListItem = record
sName : String;
iValue : Integer;
NextItem : pNextItem;
end;
The idea behind linked lists is to give us the possibility to store the address to the next
linked item in a list inside a NextItem record field. For more on data structures
consider the book: "The Tomes of Delphi: Algorithms and Data Structures".
Pointers to records can also be used when storing custom data for every tree view
item, for example.
Procedural and Method Pointers
Another important pointer concept in Delphi are procedure and method pointers.
Pointers that point to the address of a procedure or function are called procedural
pointers. Method pointers are similar to procedure pointers. However, instead of
pointing to stand-alone procedures, they must point to class methods. Method pointer
is a pointer that contains information about the name of the method that is being
invoked as well as the object that is being invoked. To see some function pointer in
action go see: "Dynamic World of Packages". Here's how to solve: "Incompatible
type: 'method pointer and regular procedure".
Pointers and Windows API
The most common use for pointers, in Delphi, is interfacing to C and C++ code,
which includes accessing the Windows API. Windows API functions use a number of
data types that may be unfamiliar to the Delphi programmer. Most of the parameters
in calling API functions are pointers to some data type. As stated above, we use null-
terminated strings in Delphi when calling Windows API functions. In many cases
when an API call returns a value in a buffer or a pointer to a data structure, these
buffers and data structures must be allocated by the application before the API call is
made. For example, take a look at theSHBrowseForFolder Windows API function.
Pointer and Memory Allocation
The real power of pointers comes from the ability to set aside memory while the
program is executing. I would not like to bother you with heaps and memory
programming, for now the next piece of code should be enough to prove that working
with pointers is not so hard as it may seem.
The following code is used to change the text (caption) of the control with the Handle
provided.
procedure GetTextFromHandle(hWND: THandle) ;
var pText : PChar; //a pointer to char (see above)
TextLen : integer;
begin
{get the length of the text}
TextLen:=GetWindowTextLength(hWND) ;

{alocate memory}
GetMem(pText,TextLen) ; // takes a pointer

{get the control's text}


GetWindowText(hWND, pText, TextLen + 1) ;

{display the text}


ShowMessage(String(pText))

{free the memory}


FreeMem(pText) ;
end;
Recursions in Delphi
Recursion is a very simple, yet useful and powerful programmer's tool. As we
know,subroutines (functions and procedures) can, and frequently do, call other
subroutines. A subroutine that activates / calls itself is called recursive.
Recursion is a general method of solving problems by reducing them to simpler
problems of a similar type. A recursive subroutine constantly calls itself, each time in
a simpler situation, until it gets to the trivial case, at which point it stops.
Many programmers often avoid this type of subroutine because it can be confusing
and complicated. This article is going to make recursion in Delphi simple ... I hope :)
Recursions in Delphi
In Delphi, there are actually two types of recursions possible. In the first, the
subroutine only calls itself. This is called direct recursion. Using direct recursion in
Delphi is simple, just call the subroutine the way you would call any other. The
second type is called mutual recursion. Mutual recursion occurs when routine A calls
routine B, and then while routine B is running, routine B calls routine A.
Direct recursions
The general algoithm for a recursive solution to a problem looks like:
1. Solve recursively (problem)
2. If the problem is trivial, do the obvious
3. Simplify the problem
4. Solve recursively (simplified problem)
What follows are some of the recursive function examples:
Calculating Factorial
"If the integer number is less than zero, reject it. If the number is zero or one, its
factorial is one. If the number is larger than one, multiply it by the factorial of the next
smaller number."
In other words: Fact(n) : = n Fact(n-1) if n > 1 otherwise Fact(n) := 1.
function Fact(inbr:integer):integer;
begin
if inbr < 1 then Result := 1
else Result := inbr * Fact(inbr-1) ;
end;
Greatest common divisor
In mathematics, GCD or greatest common divisor is defined as the largest number
that divides both of two given integer numbers. Around 2,000 years ago, Euclid gave
the following method of computing the GCD of two integers, a and b:
If b divides a, then the GCD is a. Otherwise GCD(a,b):=GCD(b, a mod b)
Where the mod function gives the reminder after integer division.
function GCD(a,b : integer):integer;
begin
if (b mod a) = 0 then Result := a
else Result := GCD(b, a mod b) ;
end;
Exponents m^n
The problem is: how to calculate mN if N is some positive integer number.
function iPow(base, exp: integer): integer;
begin
if exp = 0 then Result := 1
else Result := base * iPow(base, exp - 1) ;
end;
Recursive file search
When looking for files, it is often useful (necessary) to search through subfolders. In
theSearching for Files article you can see how to use Delphi's strength to create a
simple, but powerful, find-all-matching-files project.
Mutual recursions
You know that in Delphi we can't use an identifier until we have declared it. In other
words, Delphi doesn't let one routine call another routine that is defined after it. To
make such mutual recursion possible, we need to declare a routine as a forward
declaration. To make a forward declaration, add the forward reserved word at the end
of the header of the subroutine. Then place the header with the extra forward
declaration before the code for the subroutine that will call it. Here's an example of
using a forward declaration:
procedure MyProc1(dummy : integer) ; forward;

procedure MyProc2;
begin
MyProc1(5) ;
end;

procedure MyProc1(dummy : integer) ;


var
j:integer;
begin
for j:= 1 to dummy do
ShowMessage(IntToStr(j)) ;
end;
Although recursion looks confusing the first time through, spend some time reviewing
those code examples and in no time you won't know how you worked without it.
It is extremely important to design recursive functions with great care. If you even
suspect that there is any chance of an infinite recursion, you can have the function
count the number of times it calls itself, and thus make sure that if the function calls
itself too many times, however many you decide that should be, it automatically quits.

Some exercises for you...


Since this Course is an online course, there is much you can do to prepare for the next
chapter. At the end of each chapter I'll try to provide several tasks for you to get more
familiar with Delphi and the topics we discuss in the current chapter.
Welcome to the seventh chapter of the FREE online programming course:
A Beginner’s Guide to Delphi Programming.
Time to extend your Delphi Pascal knowledge to the max. Here are some intermediate
Delphi problems and articles for everyday development tasks.
Delphi Pascal: a step forward
Here's a list of tutorials, and articles, that will help you learn more of Delphi Pascal.
Each tutorial will help you to better understand a particular feature of Delphi Pascal
language, with practical and easy to understand workigng code examples.
Some of the articles use Windows API calls, if you are unfamiliar with the term, do
not run away, just follow the article code and you'll understand it completely.
Base Conversions, and more
A number system defines a set of values used to represent quantity. Computer uses the
binary system. Understanding binary and hexadecimal numbers (often used to
represent values [numbers and memory addresses] in computer systems) is essential
for systems-level programming. Binary numbers are important because the computer
"works with" binary numbers -- numbers composed of two digits, 1 and 0.
Hexadecimal numbers are convenient because they allow us to handle binary numbers
easily.
Delphi provides many useful functions for converting integer (decimal) values to
strings (way to represent hexadecimal and binary numbers) and vice versa.
Let's see what functions we can use to convert numbers from one base to another,
what functions are missing and how to easily implement them in Object
Pascal. IntToHex, HexToInt
In the SysUtils unit there is an IntToHex function that returns the hex representation
of an integer.
function IntToHex (Value: Integer; Digits: Integer): string;
Ok, we have the ability to convert integer to hexadecimal, but where is the HexToInt
function? There is no HexToInt function in Delphi??.
Nevertheless, Delphi allows expressions with hexadecimal notation (using a $ prefix).
So, here is a simple HexToInt function:
~~~~~~~~~~~~~~~~~~~~~~~~~
function HexToInt(HexNum: string): LongInt;
begin
Result:=StrToInt('$' + HexNum) ;
end;

~~~~~~~~~~~~~~~~~~~~~~~~~
IntToBin, BinToInt
First, to be more familiar with binary numbers, why not read: An Introduction to
Binary Arithmetic.
Like HexToInt, there are no IntToBin and BinToInt functions in Delphi. Therefore,
here they are:
~~~~~~~~~~~~~~~~~~~~~~~~~
function IntToBin ( value: LongInt; digits: integer ): string;
begin
result := StringOfChar ( '0', digits ) ;
while value > 0 do begin
if ( value and 1 ) = 1 then
result [ digits ] := '1';
dec ( digits ) ;
value := value shr 1;
end;
end;

function BinToInt(Value: String): LongInt;


var i: Integer;
begin
Result:=0;
//remove leading zeroes
while Copy(Value,1,1)='0' do
Value:=Copy(Value,2,Length(Value)-1) ;
//do the conversion
for i:=Length(Value) downto 1 do
if Copy(Value,i,1)='1' then
Result:=Result+(1 shl (Length(Value)-i)) ;
end;
~~~~~~~~~~~~~~~~~~~~~~~~~
Romans
The number system in most common use today is the Arabic system. The ten single-
digit numerals, "0" through "9", make up the symbols of our numbering system. An
example of a different numeral system is the system of Roman numerals, which could
represent all the numbers from 1 to 1,000,000 using only seven symbols: I = 1, V = 5,
X = 10, L = 50, C = 100, D = 500, M = 1000. Example: 1999 (Arabic) is MCMXCIX
(Roman).
~~~~~~~~~~~~~~~~~~~~~~~~~
function IntToRoman(Value: LongInt): String;
const
Arabics: Array[1..13] of Integer = 1,4,5,9,10,40,50,90,100,400,500,900,1000) ;
Romans: Array[1..13] of String = ('I','IV','V','IX','X','XL','L','XC','C','CD','D','CM','M')
;
var
j: Integer;
begin
for j := 13 downto 1 do
while (Value >= Arabics[j]) do begin
Value := Value - Arabics[j];
Result := Result + Romans[j];
end;
end;
~~~~~~~~~~~~~~~~~~~~~~~~~
Disk in drive A:
Floppy disk?
Yes, there might be a situation when you will need to programmatically access and
operate on the floppy disk, even today.
SetErrorMode The SetErrorMode API function controls how the operating system
handles several types of serious errors. When SEM_FAILCRITICALERRORS is set
the operating system does not display the critical-error-handler message box when
such an error occurs. Instead, the operating system sends the error to the calling
process.
DiskSize Function returns the size in bytes of the specified drive number, where 0 =
Current, 1 = A, 2 = B, etc. DiskSize returns -1 if the drive number is invalid.
DiskFree Function returns the number of free bytes on the specified drive number,
where 0 = Current, 1 = A, 2 = B, and so on. DiskFree returns -1 if the drive number is
invalid.
ShFormatDrive SHFormatDrive function is not documented in the help files neither
is it declared by Delphi. We will have to declare this function form the Shell32.dll. It
provides access to the Widows format dialog box. To declare functions from a DLL,
append the reserved word external and the name of the DLL to the end of a normal
procedure or function header. You will also have to add calling convention of stdcall
since we are using a WIN32 API DLL. As I am using this function only in this one
unit, I declare it just before the procedure that will use it.
Is Disk in Drive?
Before trying to save or load a file you can "quietly" check if a floppy drive has a
floppy disk in it and present the user with a meaningful error message rather than just
getting a critical error box that Windows displays.
procedure TForm1.Button1Click(Sender: TObject) ;
var
EMode: Word;
begin
EMode := SetErrorMode(SEM_FAILCRITICALERRORS) ;
try
if DiskSize(Ord('A')-$40) <> -1 then
ShowMessage('Disk <b>in</b> drive A: !')
else
ShowMessage('No disk <b>in</b> drive A: !') ;
finally
SetErrorMode(EMode) ;
end;
end;
Disk free space
If you need to do a backup on a floppy disk, it is a good idea to check amount of free
disk space.
procedure TForm1.Button2Click(Sender: TObject) ;
var
Drive: Byte;
sFD, sSD : string;
DFree, DSize : int64;
begin
Drive:=1;
DFree:=DiskFree(Drive) ;
DSize:=DiskSize(Drive) ;
if (DFree <> -1) and (DSize <> -1) then
begin
sFD:='Disk Free: '+IntToStr(DFree div 1024)+' Kb';
sSD:='Disk Size: '+IntToStr(DSize div 1024)+' Kb';
ShowMessage(sFD + #13 + sSD) ;
end;
end;
Windows format
The idea here is to handle disk formatting using the same dialog box that the Shell
uses. If you want to, you can even silently use DOS format function.
Note: add ShellApi to unit's uses clause.
procedure TForm1.Button4Click(Sender: TObject) ;
const
SHFMT_DRV_A = 0;
SHFMT_DRV_B = 1;
SHFMT_ID_DEFAULT = $FFFF;
SHFMT_OPT_QUICKFORMAT = 0;
SHFMT_OPT_FULLFORMAT = 1;
SHFMT_OPT_SYSONLY = 2;
SHFMT_ERROR = -1;
SHFMT_CANCEL = -2;
SHFMT_NOFORMAT = -3;
var
FmtRes : LongInt;
begin
try
FmtRes:=ShFormatDrive(Handle,
SHFMT_DRV_A,
SHFMT_ID_DEFAULT,
SHFMT_OPT_QUICKFORMAT) ;
case FmtRes of
SHFMT_ERROR:
ShowMessage('Error formatting the drive') ;
SHFMT_CANCEL:
ShowMessage('User canceled formatting the drive') ;
SHFMT_NOFORMAT:
ShowMessage('Drive is not formatable')
else
ShowMessage('Disk has been formatted') ;
end;
except
ShowMessage('Error occurred!')
end;
end;
Final words
In some cases, you will need to handle floppy disk. These functions are here to help
you, and to save the time and energy you might spend in searching for right solution.
Don't panic if you don't understand some of the code here, try to play with it for a
while, and you'll see that Windows API is not so hard as it seems.
Note: It is very easy to implement such procedures (some of them) to work with CD-
ROM or any other removable media.
Start from Delphi
How many times have you wanted to execute some program from your Delphi
application?
Let's say we have a database application that uses some external backup utility. The
back up utility takes parameters from your application, archives data, while your
program waits until backup finishes.
On the other hand, have you ever needed to open documents presented in a file
listbox, just by double clicking on them, without opening the associated program
first?
Finally, imagine a link-label in your program that will take the user to your home page
(by executing the Internet Explorer). Moreover, what do you say about sending an e-
mail directly from your Delphi application through default Windows e-mail client
program (like MS Outlook Express).
ShellExecute
To launch an application or execute a file in Win32 environment we will use the
ShellExecute Windows API function. Check out the help on ShellExecute for full
description of parameters and error codes returned.
As you will see we can open any type of document from our program without
knowing which program is associated with it (this link is defined in the Windows
Registry).
Let's see some shell action!
Be sure to add ShellApi to your Unit's uses clause.

Run Notepad
uses ShellApi;
...
ShellExecute(Handle, 'open', 'c:\Windows\notepad.exe', nil, nil,
SW_SHOWNORMAL) ;
Open SomeText.txt with Notepad
ShellExecute(Handle,'open', 'c:\windows\notepad.exe','c:\SomeText.txt', nil,
SW_SHOWNORMAL) ;
Display the contents of the "DelphiDownload" folder
ShellExecute(Handle,'open', 'c:\DelphiDownload', nil, nil, SW_SHOWNORMAL) ;
Execute a file according to its extension.
ShellExecute(Handle, 'open',
'c:\MyDocuments\Letter.doc',nil,nil,SW_SHOWNORMAL) ;
Here's how to Find an application associated with an extension
Open web site or a *.htm file with the default web explorer
ShellExecute(Handle, 'open', 'http://delphi.about.com',nil,nil,
SW_SHOWNORMAL) ;
Here's how to Open a Web Browser in a new window
Send an e-mail with the subject and the message body
var em_subject, em_body, em_mail : string;
begin
em_subject := 'This is the subject line';
em_body := 'Message body text goes here';

em_mail := 'mailto:delphi.guide@about.com?subject=' +
em_subject + '&body=' + em_body ;

ShellExecute(Handle,'open',
PChar(em_mail), nil, nil, SW_SHOWNORMAL) ;
end;
Here's how to send an email with the attachment
Execute a program and wait until it has finished. The following example uses the
ShellExecuteEx API function.
// Execute the Windows Calculator and pop up
// a message when the Calc is terminated.
uses ShellApi;
...
var
SEInfo: TShellExecuteInfo;
ExitCode: DWORD;
ExecuteFile, ParamString, StartInString: string;
begin
ExecuteFile:='c:\Windows\Calc.exe';

FillChar(SEInfo, SizeOf(SEInfo), 0) ;
SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
with SEInfo do begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := Application.Handle;
lpFile := PChar(ExecuteFile) ;
{
ParamString can contain the
application parameters.
}
// lpParameters := PChar(ParamString) ;
{
StartInString specifies the
name of the working directory.
If ommited, the current directory is used.
}
// lpDirectory := PChar(StartInString) ;
nShow := SW_SHOWNORMAL;
end;
if ShellExecuteEx(@SEInfo) then begin
repeat
Application.ProcessMessages;
GetExitCodeProcess(SEInfo.hProcess, ExitCode) ;
until (ExitCode <> STILL_ACTIVE) or
Application.Terminated;
ShowMessage('Calculator terminated') ;
end
else ShowMessage('Error starting Calc!') ;
end;
Auto completing Dates
Components like TDateTimePicker and similar that are designed specifically for
entering dates or times are useful when we want some visual representation of a date.
However, why bother with visual components that are 'not-so-input-friendly when
everything we want is just one Edit box to type the date into.
In this article you will find one great function designed to make date processing a bit
faster.
Date Manipulation
The following DateComplete function is used to auto complete the given string value
to a date representation.
The whole idea is to allow our users to only start typing the date and to finish it
from code when appropriate (in most cases in the OnExit event of some edit-enabled
control.)
For example, if the default Windows short date format is 'dd.mm.yyyy' then assigning
'29' to the StrLikeDate variable will, after calling the DateComplete procedure, result
in '29.01.2006'. Of course, this is all true if the current month and year are January,
2006.
procedure DateComplete(var StrLikeDate :string; const NowIfError:boolean =
True) ;
var
DateStr : string;
Year, Month, Day : Word;
cnt, SepCount : Integer;
begin
DateStr:=StrLikeDate;

if DateStr = '' then Exit;


SepCount := 0;

for cnt := 1 to Length(DateStr) do


begin
if not (DateStr[cnt] in ['0'..'9']) then
begin
DateStr[cnt] := DateSeparator;
inc(SepCount) ;
end;
end;

while (DateStr <> '') and (DateStr[Length(DateStr)]=DateSeparator) do


begin
Delete(DateStr, Length(DateStr), 1) ;
Dec(SepCount) ;
end;

DecodeDate(Now, Year, Month, Day) ;

if SepCount = 0 then
begin
case Length(DateStr) of
0 : DateStr := DateToStr(Now) ;
1, 2 : DateStr := DateStr+DateSeparator+IntToStr(Month) ;
4 : Insert(DateSeparator, DateStr, 3) ;
6, 8 : begin
Insert(DateSeparator, DateStr, 5) ;
Insert(DateSeparator, DateStr, 3) ;
end;
end; {case}
end; {if SepCount}

try
StrLikeDate := DateToStr(StrToDate(DateStr)) ;
except
if NowIfError = true then
StrLikeDate := DateToStr(Date)
else
StrLikeDate := '';
end;
end;
The DateComplete Procedure
As we can see from the procedure header:
procedure DateComplete(var StrLikeDate : string; const NowIfError:boolean =
True) ;
The DateComplete takes the StrLikeDate string parameter by reference using the var
keyword, meaning that DateComplete function is able to alter the value in
StrLikeDate. The second parameter (optional, defaults to True) NowIfError is used in
the exception handler part to check for bad date (for example: letters in StrLikeDate).
If NowIfError is True (default if omitted) and there is an error while trying to convert
the StrLikeDate value to date representation, the value of StrLikeDate will become
the current date (Now), otherwise (NowIfError = False) the StrLikeDate will be
returned as an empty string ('').
With the previous discussion in our mind, the DateComplete function can be called
as:
procedure TForm1.edDateExit(Sender: TObject) ;
var
s : string;
begin
s := edDate.Text;
DateComplete(s, True) ; {or DateComplete(s)}
edDate.Text := s;
end;
Note: The DateComplete procedure will alter the StrLikeDate value to a date format
'dd.mm.yyyy', where date separator '.' is stored in the global variable DateSeparator
(Delphi reads this value from the Regional Settings in the Control Panel).
mm/dd/yyyy ?
If you (your users) are not using the 'dd.mm.yyyy' but 'mm/dd/yyyy' date format then
DateComplete needs some modification to work properly. Take a look at the next line:
case Length(DateStr) of
...
1, 2 : DateStr:=DateStr + DateSeparator + IntToStr(Month) ;
...
This piece of code handles the situation when only one or two digits are entered - only
day (remember: 'dd.mm.yyyy'). DateStr becomes DateStr + Month (current).
If you are working with 'mm/dd/yyyy' short date format, simply change the previous
code to:
case Length(DateStr) of
...
1, 2 : DateStr:=DateStr + DateSeparator + IntToStr(Day) ;
...
That's it.
NO GUI Delphi applications
Console applications are pure 32-bit Windows programs that run without a
graphical interface. When a console application is started, Windows
creates a text-mode console window through which the user can interact
with the application. These applications typically don't require much user
input. All the information a console application needs can be provided
through command line parameters.
For students, console applications will simplify learning Pascal and Delphi - after all,
all the Pascal introductory examples are just console applications.
New ... Console application
Here's how to quickly build console applications that run without a graphical
interface.
If you have a Delphi version newer than 4, than all you have to do is to use the
Console Application Wizard. Delphi 5 introduced the console application wizard. You
can reach it by pointing to File|New, this opens up a New Items dialog - in the New
page select the Console Application. Note that in Delphi 6 the icon that represents a
console application looks different. Double click the icon and the wizard will setup a
Delphi project ready to be compiled as a console application.
While you could create console mode applications in all 32-bit versions of Delphi, it's
not an obvious process. Let's see what you need to do in Delphi versions <=4 to create
an "empty" console project. When you start Delphi, a new project with one empty
form is created by default. You have to remove this form (a GUI element) and tell
Delphi that you want a console mode app. This is what you should do:
0. Select "File | New Application"
1. Select "Project | Remove From Project..."
2. Select Unit1 (Form1) and click OK. Delphi will remove the selected unit from the
uses clause of the current project.
3. Select "Project | View Source"
4. Edit your project source file:
• Delete all the code inside "begin" and "end".
• After the uses keyword, replace the "Forms" unit with "SysUtils".
• Place {$APPTYPE CONSOLE} right under the "program" statement.
You are now left with a very small program which looks much like a Turbo Pascal
program which, if you compile it will produce a very small EXE. Note that a Delphi
console program is not a DOS program because it is able to call Windows API
functions and also use its own resources. No matter how you have created a skeleton
for a console application your editor should look like:
This is nothing more than a "standard" Delphi project file, the one with the .dpr
extension.
. The program keyword identifies this unit as a program's main source unit. When we
run a project file from the IDE, Delphi uses the name of the Project file for the name
of the EXE file that it creates - Delphi gives the project a default name until you save
the project with a more meaningful name.
. The $APPTYPE directive controls whether to generate a Win32 console or
graphical UI application. The {$APPTYPE CONSOLE} directive (equivalent to
the /CC command-line option), tells the compiler to generate a console application.
. The uses keyword, as usual, lists all the units this unit uses (units that are part of a
project). As you can see, the SysUtils unit is included by default. Another unit is
included too, the System unit, though this is hidden from us.
. In between the begin ... end pair you add your code.
Ok. Now when you know how a console application looks like it's time for a real
example. Let's create a simple game!
Guess A Number
Let's create a simple console game. The computer will randomly pick an integer
number from 0 to 50. Your task is to guess the number. Each time you pick a number
a program will tell that your number is larger or smaller than the one you are looking
for. When, finally, you find what the number was it'll tell you how many times it took
you to find it.
I'll give you the code, but first let's see what are two most seen commands in a
console application:
Write and Read RTL procedures are typically used for writing and reading from a file.
There are two standard text-file variables, Input and Output. In a Console application,
Delphi automatically associates the Input and Output files with the application's
console window. The standard file variable Input is a read-only file associated with
the operating system's standard input (typically the keyboard). The standard file
variable Output is a write-only file associated with the operating system's standard
output (typically the display).
Thus, Writeln is used to display a message; ReadLn is normally used to read in
variables. In the code below, you will notice it ends with a ReadLn. Readln without
any parameters simply waits for the [Enter] key to be pressed. One of the peculiarities
of a console application is that when it has stopped running, the console window is
automatically closed. The ReadLn statement is necessary so that the user can see any
text produced by Writeln statements before it disappears off the screen when the
program finishes.
Here goes the code. I hope you understand it.
program GuessANumber;
{$APPTYPE CONSOLE}
uses
SysUtils;

var
rn, un, cnt: Integer;
guess: Boolean;

begin
Randomize;
rn := Trunc(Random(50) + 1);
Write('Computer has picked an integer number,');
Write('from 1 to 50, guess the number!');
WriteLn('Your guess is: ');
cnt := 1; un := 0;
Guess := False;
while Guess = False do begin
ReadLn(un);
if un > rn then
Write('Wrong, gimme a smaller number: ')
else if un < rn then
Write('Wrong, gimme a larger number: ')
else //un=rn
begin
Guess:=True;
Writeln;
Write('Correct! It took you ' +
IntToStr(cnt) +
' times to guess!')
end;
cnt := cnt + 1;
end; //while
ReadLn; //don't close the window, wait for [Enter]
end.
Simply run the project and play....

Console applications: Tips and Tricks


Since Console applications are not standard Delphi projects, you should be familiar
with several situations that evolve when working with console windows.
 Here's how to Capture the Output From a Console mode application and how
toDetermine the output of a console application.
 Console applications should be written to handle all exceptions - to prevent
windows from displaying a dialog during its execution. "Simply" add all the code
inside atry/except/finally blocks.
 To return an exit code to the calling application or batch file from a Windows
Console mode application written in Delphi call the Halt procedure, passing the exit
code you wish to return.
 There is a IsConsole variable defined in the Sytsem unit that indicates whether the
module was compiled as a console application.
 If you start a Console application form the command prompt or a batch file you
have to use the start command to create a new console window for the application.
Otherwise it takes over the DOS prompt window from which it was started.
 Console applications also provide sophisticated low-level API support that gives
direct access to a console's screen buffer and that enables applications to receive
extended input information (such as mouse input). For example, the
SetConsoleTextAttribute function sets the foreground (text) and background color
attributes of characters written to the screen. Note that you need to add the Windows
unit to the uses clause in order to use the API calls. The next peace of code could be
used to display red text on a blue background:
SetConsoleTextAttribute(GetStdHandle(
STD_OUTPUT_HANDLE),
FOREGROUND_RED OR
BACKGROUND_BLUE);

Writeln('Red text on a blue background !');


 Many of the nice things that conventional Pascal uses to manage the text screen,
like clrscr, gotoxy and so on are not available in a console-based application. Again,
Win API has all we need. Here are some examples. As you will see, you can even
make a "message loop".
 If you want to have console input/output for GUI applications you should use the
AllocConsole and FreeConsole functions. The AllocConsole function allocates a new
console for the calling process. The FreeConsole function detaches the calling process
from its console. Here's the example:
var s: string;
begin
AllocConsole;
try
Write('Type something and press [ENTER]');
Readln(s);
ShowMessage('You typed ' + s);
finally
FreeConsole;
end;
end;
 If you have ever tried to run a console application and capture the output, you may
have tried to make sense of the topic in the Win32 help files called 'Creating a child
process with redirected input and output'. The tip called "Capture the Output From a
DOS Window" solves the problem. Here is another example of redirecting a console
application output.
 Screen scraping is a technique for "grabbing" text from the screen to another
application, or simple converting character images on the screen back to text. Screen
scraping is often used to convert old terminal applications to the web by running the
terminal software in the background and letting a web front-end do all the
manipulation of the older application. In cases you only need simple screen scraping
techniques, you can easily use the built-in Delphi VCL components to convert
character images on a canvas to text, see how.
 Se sure to check the Delphi WDosX Project, a freeware 32-bit DOS extender,
which enables 32-bit programs, to be executed independently from Windows with
plain DOS. For this purpose WDosX makes available a DPMI interface, an extended
DOS Int 21 API interface, as well as the Windows core DLLs Kernel32 and User32 to
the 32-bit program. Additionally, WDosX includes some useful libraries, sample
programs and a full screen debugger.
That's it

Text Files in Delphi


Simply put, text files contain readable ASCII characters. We can think of working
with text file in Delphi as analogous to playing or recording information on a VCR
tape.
Although it is possible to make changes within text file, jump around when processing
information or add some data to the file other than at the end, it is advisable to use a
text file only when we know that we are working with ordinary text and no such
operations are necessary.
Text files are considered to represent a sequence of characters formatted into lines,
where each line is terminated by an end-of-line marker (a CR/LF combination).
The TextFile and the Assign Method
To start working with text files you have to link a file on a disk to a file variable in
your code - declare a variable of type TextFile and use the AssignFile procedure to
associate a file on a disk with a file variable.
var
SomeTxtFile : TextFile;
begin
AssignFile(SomeTxtFile, FileName)
Reading information from a Text File
If we want to read back the contest of a file into a string list, just one line of code will
do the job.
Memo1.Lines.LoadFromFile('c:\autoexec.bat')
To read information from a file line by line, we must open the file for input by using
the Reset procedure. Once a file is reset, we can useReadLn to read information from
a file (reads one line of text from a file then moves to the next line) :
var
SomeTxtFile : TextFile;
buffer : string;
begin
AssignFile(SomeTxtFile, 'c:\autoexec.bat') ;
Reset(SomeTxtFile) ;
ReadLn(SomeTxtFile, buffer) ;
Memo1.Lines.Add(buffer) ;
CloseFile(SomeTxtFile) ;
end;
After adding one line of text from a file to a memo component SomeTxtFile needs to
be closed. This is done by the Close keyword.
We can also use Read procedure to read information from a file. Read works just like
ReadLn, except it does not move the pointer to the next line.
var
SomeTxtFile : TextFile;
buf1,buf2 : string[5];
begin
AssignFile(SomeTxtFile, 'c:\autoexec.bat') ;
Reset(SomeTxtFile) ;
ReadLn(SomeTxtFile, buf1,buf2) ;
ShowMessage(buf1 + ' ' +buf2) ;
CloseFile(SomeTxtFile) ;
end;
EOF - End Of File
Use the EOF function to make sure that you are not trying to read beyond the end of
the file. Let's say we want to display the contest of the file in message boxes - one line
at a time, until we get to the end of a file:
var
SomeTxtFile : TextFile;
buffer : string;
begin
AssignFile(SomeTxtFile, 'c:\autoexec.bat') ;
Reset(SomeTxtFile) ;
while not EOF(SomeTxtFile) do
begin
ReadLn(SomeTxtFile, buffer) ;
ShowMessage(buffer) ;
end;
CloseFile(SomeTxtFile) ;
end;
Note: It is better to use While loop than the Until loop to take into account the
(unlikely) possibility that the file exists but does not contain any data.
Writing Text to a File
The WriteLn is probably the most common way to send individual pieces of
information to a file. The following code will read a text from a Memo1 component
(line by line) and send it to some newly created text file.
var
SomeTxtFile : TextFile;
j: integer;
begin
AssignFile(SomeTxtFile, 'c:\MyTextFile.txt') ;
Rewrite(SomeTxtFile) ;
for j := 0 to (-1 + Memo1.Lines.Count) do
WriteLn(SomeTxtFile, Memo1.Lines[j]) ;
CloseFile(SomeTxtFile) ;
end;
Depending on the state of the file provided to the Rewrite procedure it creates a new
file (opens the file for output) with the name assigned to SomeTextFile. If a file with
the same name already exists it is deleted and a new empty file is created in its place.
If SomeTextFile is already open, it is first closed and then re-created. The current file
position is set to the beginning of the empty file.
Note: Memo1.Lines.SaveToFile('c:\MyTextFile.txt') will do the same.
Sometimes we'll just need to add some text data to the end of an existing file. If this is
the case, we'll call Append to ensure that a file is opened with write-only access with
the file pointer positioned at the end of the file. Something like:
var
SomeTxtFile : TextFile;
begin
AssignFile(SomeTxtFile, 'c:\MyTextFile.txt') ;
Append(SomeTxtFile) ;
WriteLn(SomeTxtFile, 'New line in my text file') ;
CloseFile(SomeTxtFile) ;
end;
Be Aware of Exceptions
In general, you should always use exception handling when working with files. I/O is
full of surprises. Always use CloseFile in a finally block to avoid the possibility of
corrupting a user's FAT. All the previous examples should be rewritten as follows:
var
SomeTxtFile : TextFile;
buffer : string;
begin
AssignFile(SomeTxtFile, 'c:\MyTextFile.txt') ;
try
Reset(SomeTxtFile) ;
ReadLn(SomeTxtFile, buffer) ;
finally
CloseFile(SomeTxtFile) ;
end;
end;
Manipulating with Structured Files
Delphi has the ability to handle both ASCII files and files that hold binary data. Here
are the techniques for working with typed and untyped (binary) files.
Scenario: In my Delphi program I need a way of storing some information to the disk.
I do not want to work with text files - there should be no easy way to see/alter the
information in a file. There may be actions like changing/updating the existing data;
but mainly writing and reading will take place. All the information is stored in my
user-defined Delphi recorddata type. What approach should I use?
BDE or ADO plus Paradox or Access, ... well no thanks ... I do not want to bother
with the BDE. Should I use the ASCII text files approach, hm no, there should be
some minimal protection - text files are "to visible". Delphi has the answer! The
answer is: typed files (orfiles of some type/binary files).
Files
Simply put a file is a binary sequence of some type. In Delphi there a three classes of
file:typed, text, and untyped. Typed files are files that contain data of a particular
type, such as Double, Integer or previously defined custom Record type. Text files
contain readable ASCII characters. Untyped files are used when we want to impose
the least possible structure on a file.
Typed Files
While text files consist of lines terminated with a CR/LF (#13#10)combination, typed
files consist of data taken from a particular type of data structure.
For example, the following declaration creates arecord type called TMember and an
array of TMember record variables.
type
TMember = record
Name : string[50];
eMail : string[30];
Posts : LongInt;
end;

var Members : array[1..50] of TMember;


Before we can write the information to the disk we have to declare a variable of a file
type. The following line of code declares an F file variable.
var F : file of TMember;
Note: To create a typed file in Delphi, we use the following syntax:
var SomeTypedFile : file of SomeType
The base type (SomeType) for a file can be a scalar type (like Double), an array type
or record type. It should not be long string, dynamic array, class, object or a pointer.
In order to start working with files from Delphi we have to link a file on a disk to a
file variable in our program. To create this link we must use AssignFile procedure in
order to associate a file on a disk with a file variable.
AssignFile(F, 'Members.dat')
Once the association with an external file is established, the file variable F must be
'opened' to prepare it for reading and/or writing. We call Reset procedure to open an
existing file or Rewrite to create a new file. When a program completes processing a
file, the file must be closed using the CloseFile procedure. After a file is closed, its
associated external file is updated. The file variable can then be associated with
another external file.
In general, we should always use exception handling; many errors may arise when
working with files. For example: if we call CloseFile for a file that is already closed
Delphi reports an I/O error. On the other hand, if we try to close a file but have not yet
called AssignFile, the results are unpredictable.
Write to a File
Suppose we have filled an array of Delphi members with their names, e-mails and
number of posts and we want to store this information in a file on the disk. The
following peace of code will do the work:
var
F : file of TMember;
i : integer;
begin
AssignFile(F,'members.dat') ;
Rewrite(F) ;
try
for j:= 1 to 50 do
Write (F, Members[j]) ;
finally
CloseFile(F) ;
end;
end;
Read from a File
In order to retreive all the information from the 'members.dat' file we could use this
code:
var
Member: TMember
F : file of TMember;
begin
AssignFile(F,'members.dat') ;
Reset(F) ;
try
while not Eof(F) do begin
Read (F, Member) ;
{DoSomethingWithMember;}
end;
finally
CloseFile(F) ;
end;
end;
Note: Eof is the EndOfFile checking function. We use this function to make sure that
we are not trying to read beyond the end of the file (beyond the last stored record).
Seeking and Positioning
Files are normally accessed sequentially. When a file is read using the standard
procedure Read or written using the standard procedure Write, the current file position
moves to the next numerically ordered file component (next record). Typed files files
can also be accessed randomly through the standard procedure Seek, which moves the
current file position to a specified component. The FilePos and FileSize functions can
be used to determine the current file position and the current file size.
{go back to the beginning - the first record}
Seek(F, 0) ;

{go to the 5-th record}


Seek(F, 5) ;

{Jump to the end - "after" the last record}


Seek(F, FileSize(F)) ;
Change and Update
We just learned how to write and read the entire array of Members. What when all we
want to do is to seek to the 10-th member and change his e-mail? The next procedure
does exactly that:
procedure ChangeEMail(const RecN : integer; const NewEMail : string) ;
var DummyMember : TMember;
begin
{assign, open, exception handling block}
Seek(F, RecN) ;
Read(F, DummyMember) ;
DummyMember.Email := NewEMail;
{read moves to the next record, we have to
go back to the original record, then write}
Seek(F, RecN) ;
Write(F, DummyMember) ;
{close file}
end;
All done!
That's it, now we have all we need to accomplish our task. We can write Members
information to the disk, we can read it back and we can even change some of the data
(e-mail, for example) in the "middle" of the file.
What's important is that this file is not an ASCII file, this is how it looks in Notepad
(only one record):
.Delphi Guide g Ò5·¿ì. 5. . B V.Lƒ ,„¨.delphi.guide@about.comÏ.. ç.ç.ï..
Next time we'll see how to use untyped files in Delphi - those that impose the least
possible structure on a file, or have no structure whatsoever. Untyped files are most
commonly used when doing direct access to disk files, that is, operations like copying.
Errors and Exceptions
Unfortunately, building applications includes coding. Regardless of how carefully you
write/debug your program, it will be impossible to imagine every situation that can go
wrong. Inexperienced user might, for example, try to open a nonexisting file or input
a bad value into a data field.
Users make mistakes and we should be prepared to handle/prevent these errors
wherever and whenever possible.
Errors, Exceptions?
An exception is generally an error condition or other event that interrupts normal flow
of execution in an application. Whenever an error results from processing a line of
code, Delphi creates (raises) an object descendent from TObject called the exception
object.
Guarded Blocks
An application responds to an exception either by executing some termination code,
handling the exception, or both. The way to enable error/exception trapping within a
given code, the exception must occur within a guarded block of statements. The
general code looks like:
try
{guarded block of code}
except
on <ESomeException> do begin
{exception block-handles SomeException}
end;
end;
A try / except statement executes the statements in the guarded block of code. If the
statements execute without any exceptions being raised, the exception block is
ignored, and control is passed to the statement following the end keyword.
Example:
...
Zero:=0;
try
dummy:= 10 / Zero;
except
on EZeroDivide do
MessageDlg('Can not divide by zero!',
mtError, [mbOK], 0) ;
end;
...
Protecting Resources
When a section of code acquires a resource, it is often necessary to ensure that the
resource is released again (or you might get a memory leak), regardless of whether the
code completes normally or is interrupted by an exception. In this case, the syntax
usesfinally keyword and looks like:
{some code to allocate resources}
try
{guarded block of code}
finally
{termination blok - code to free resources}
end;
Example:
...
AboutBox:=TAboutBox.Create(nil) ;
try
AboutBox.ShowModal;
finally
AboutBox.Release;
end;
...
Application.OnException
If your application doesn't handle the error that caused the exception, then Delphi will
use its default exception handler - it will just pop up a message box. You may consider
writing code in the OnException event for TApplication object, in order to trap errors
at the application level.
Break On Exceptions
When building a program with exception handling, you may not want Delphi to break
on Exceptions. This is a great feature if you want Delphi to show where an exception
has occurred; however, it can be annoying when you test your own exception
handling.
Few final words
The idea of this article is to give you just a quick look at what exceptions are. For
further discussion on exception handling, consider some of the following related
articles:
 Exceptional exceptions - Normal Exception Usage
 There are two kinds of exceptions: try..finally blocks, and try..except blocks.
Typically, you use try..finally blocks to protect resources,
and try..except blocks to handle exceptions.
 The following two examples illustrate these uses:
 procedure TForm1.Button1Click(Sender: TObject);
 var
 Strings: TStringList;
 begin
 Strings := TStringList.Create;
 try
 Strings.Add('Hello, world!');
 Strings.SaveToFile(ChangeFileExt(ParamStr(0), '.txt'));
 finally
 Strings.Free;
 end;
 end;
 procedure TForm1.Button2Click(Sender: TObject);
 var
 Strings: TStringList;
 begin
 Strings := TStringList.Create;
 try
 Strings.Add('Hello, world!');
 try
 Strings.SaveToFile(ChangeFileExt(ParamStr(0), '.txt'));
 except
 on E: Exception do begin
 ShowMessage('The following string could not be saved:'#13#10 +
 String.Text);
 end;
 end;
 finally
 Strings.Free;
 end;
 end;
 The first example shows a normal use of a try..finally block. We create
a TStringList object, and because we have acquired a resource (memory, in
this case), we want to make sure that it's released. There are many things that
could fail in the rest of the code - maybe there's not enough memory to create
a new string, or we don't have permission to write to the new file. Or we
exceed our disk quota. We don't know all the possible causes of failure, but we
do know we want the memory to be released. try..finally allows us to provide
this behaviour with small fuss.
 The second example shows a normal use of a try..except block. We can't save
the string to a file, so we change our behaviour and show the string to the user.
Note that the try..except block is embedded in the try..finally block - in this
case, we still want the memory to be released.
 Exceptional Exceptions
 Now I'll show you some other examples of code, which highlight some
interesting uses of exceptions.
 Creation Routines
 function CreateStringsFromFile(const FileName: string): TStringList;
 begin
 Result := TStringList.Create;
 try
 Result.LoadFromFile(FileName);
 except
 Result.Free;
 raise;
 end;
 end;
 This first example creates a TStringList, loaded with the contents of a file.
This routine is meant to be a replacement forTStringList.Create. Because of
this, we want to make sure we keep the same allocation semantics. Let me
elaborate on this.
 When we call a constructor, we are assured that either the call succeeds and
we get a valid object, or the call fails and all resources are released. This is
why we can place a constructor right before a try..finally block - if the
constructor raises an exception, there will be nothing to free.
 We must keep the same semantics for CreateStringsFromFile. If we raise an
exception, we want to make sure that there's nothing left to release. If we
return correctly, then we have a valid object.
 In this case, we accomplish this by using a try..except block. First, we create
the object; if this fails, we don't handle the exception, but we let bubble up. If
we get the memory successfully, then we will load the contents from a file.
Here, there are a million things that could go wrong - if an exception is
thrown, we will release the memory we allocated (because we will notbe
returning a valid object), and allow the exception to bubble up - in case our
caller can deal with it intelligently, such as prompting the user for another file
and retrying.
 Generic Creation Routine
 Trying to understand all this, remember this little rule: for routines which will
create and return an initialized object, make sure you sandwich all the code
inside a try..except block.
 function CreateWhatever: TWhatever;
 begin
 Result := TWhatever.Create;
 try
 InitializeWhatever;
 except
 Result.Free;
 raise;
 end;
 end;
 Freedom!
 Every class has a constructor, which is used to create instances of that class.
The constructor is invoked on the class itself, not on the object, which is why
we write
 Strings := TStringList.Create;
 and not
 Strings := Strings.Create;
 or
 Strings.Create;
 The Free method is a very special one: even if you invoke it on an instance
which is set to nil, it will not generate an exception. So the following code is
quite ok:
 Strings := nil;
 Strings.Free;
 This is an important thing to remember when reading the next section.
 To Protect The Many
 This next example shows how to protect many objects in with nested
exception blocks, and how to do the same thing in onetry..finally block.
 procedure TForm1.Button5Click(Sender: TObject);
 var
 Strings1: TStringList;
 Strings2: TStringList;
 Strings3: TStringList;
 begin
 Strings1 := TStringList.Create;
 try
 Strings2 := TStringList.Create;
 try
 Strings3 := TStringList.Create;
 try
 Strings1.Add('Hello, world!');
 Strings1.SaveToFile(ChangeFileExt(ParamStr(0), '.txt'));
 { This will raise an exception, but all resources will be freed correctly. }
 Strings2.Delete(51);
 finally
 Strings3.Free;
 end;
 finally
 Strings2.Free;
 end;
 finally
 Strings1.Free;
 end;
 end;

 procedure TForm1.Button4Click(Sender: TObject);
 var
 Strings1: TStringList;
 Strings2: TStringList;
 Strings3: TStringList;
 begin
 Strings2 := nil;
 Strings3 := nil;
 Strings1 := TStringList.Create;
 try
 Strings2 := TStringList.Create;
 Strings3 := TStringList.Create;
 Strings1.Add('Hello, world!');
 Strings1.SaveToFile(ChangeFileExt(ParamStr(0), '.txt'));
 { This will raise an exception, but all resources will be freed correctly. }
 Strings2.Delete(51);
 finally
 Strings1.Free;
 Strings2.Free;
 Strings3.Free;
 end;
 end;
 As you can see, the second example is shorter and clearer. However, we have
to make sure that we can process the finallyblock correctly even if it is
executed before all objects are created. Therefore, we make sure that all
pointers are assigned some value (the compiler will generate a warning if you
don't do it like this). Remember that a call to Free will do nothing if the object
has a nil value, so it's safe to call it even if the object hasn't been constructed.
 I want out!
 The next example will show you how to release a resource before waiting for
the finally block to execute.
 procedure TForm1.Button4Click(Sender: TObject);
 var
 Strings1: TStringList;
 Strings2: TStringList;
 begin
 Strings2 := nil;
 Strings1 := TStringList.Create;
 try
 Strings2 := TStringList.Create;
 Strings1.Add('Hello, world!');
 Strings1.SaveToFile(ChangeFileExt(ParamStr(0), '.txt'));
 FreeAndNil(Strings1);
 { This will raise an exception, but all resources will be freed correctly. }
 Strings2.Delete(51);
 finally
 Strings1.Free;
 Strings2.Free;
 end;
 end;
 Suppose that Strings1 was using a lot of resources, and we needn't keep it
alive until the finally block was executed. We can free it in the middle of the
protected code, but we need to make sure that the variable is set to nil -
otherwise, the program will generate an access violation when trying to free
the object twice. This is the same case as in the previous example - we make
sure that we handle uninitialized resources correctly in the finally block.
 FreeAndNil, for Delphi 5 users, performs exactly this function. Users of
previous Delphi versions can write their own routine or simply inline the code:
 Strings1.Free;
 Strings1 := nil;
 Ignoring Exceptions with Delphi's Integrated Debugger - By default, when an
exception is encountered by an application that you are running from the
Delphi IDE (integrated development environment), several things happen.
First, your program stops executing. Second, the integrated debugger loads
and details about the exception are displayed in a dialog box.
 It takes two or more steps in order to resume your program's execution. First,
you must acknowledge the exception by closing the displayed dialog box.
Next, you must press F9 or click the run button to resume the execution of
your application. Third, you must close the subsequent dialog box that
displays the exception to the end user, which in this case is you. (This third
dialog box is not displayed if the exception was a silent exception or if the
exception was caught and handled in an except block.)
 While this behavior is often welcome, there are times when I would prefer that
the integrated debugger did not load. Instead, I would prefer to simply see the
exception as the end user would see it, reducing the number of steps that it
takes to continue testing the application.
 For example, imagine that you are testing a database application, and that you
raise an exception from a ClientDataSet's BeforePost method when your code
determines that the record being posted is invalid. While testing the
application, I would prefer to only see the error message describing to the end
user why the record is being rejected without having the integrated debugger
getting involved.
 Here is another example. Imagine that you are writing an application using an
Internet Direct (Indy) TIdTCPClient component to retrieve HTML from a Web
site for parsing. With that component, you continue to call Read until it raises
an exception, which signals that the page has finished downloading and the
connection has been closed by the server.
 Having the integrated debugger load when this exception occurs is nothing
more than a nuisance. The exception is not really an error. It's really just a
signal to your program. And by default, even if you are reading from the
TIdTCPClient within a try-except clause, your application will still stop and
the integrated debugger will load.
 There are three ways to prevent the debugger from loading every time an
exception occurs. The first is to disable the debugger altogether. You do this by
unchecking the Integrated debugging check box on the Debugger Options
dialog box in Delphi 7 and Kylix. Display this dialog box, shown in the
following figure, by selecting Tools | Debugger Options.


 In Delphi 8, this checkbox is on the Options dialog box when the Debugger
Options node is selected, as shown in the following figure. Display the
Options dialog box by selecting Tools | Options from Delphi 8's main menu.

 This approach is overkill, though. Breakpoints do not work when the


integrated debugger is not enabled. Similarly, the various Debugger windows,
such as the Modules, Watches, or the Event Log are inactive when the
Integrated Debugger is disabled. Consequently, I generally do not use this
approach.
 The second technique is to configure the debugger to ignore exceptions. There
are two ways to do this. In Delphi 7 and Kylix, the easiest is to view the
Debugger Options dialog box, select the Language Exceptions tab, and
uncheck the Stop on Delphi Exceptions check box, shown in the following
figure. Now, breakpoints will load the integrated debugger, but exceptions will
not. Furthermore, Debugger windows will continue to be available.

 In Delphi 8, select the Language Exceptions node from the Options dialog
box, as shown in the following figure.

 To resume the default behavior, simply check the Stop on Delphi Exceptions
check box once again.
 The second way to disable the integrated debugger from loading in response to
an exception is to use breakpoints. I discussed this technique in the article
"Using Non-Breaking Breakpoints in Delphi," which is published here on
the Borland Developer Network. Consequently, I will not repeat that
information again in this article.
 The third technique is to tell the integrated debugger to ignore all exceptions
of a particular class or group of classes. This technique also involves the
Language Exceptions tab of the Debugger Options dialog box in Delphi 7 and
Kylix, and the Language Exceptions node from the Options dialog box in
Delphi 8.
 Here's how you do it. Leave Stop on Delphi Exceptions checked (as well as
Integrated debugging). Click the Add button on the Language Exceptions pane
and add the name of the exception class that you want the integrated debugger
to ignore.
 When an exception is raised, the integrated debugger will check to see if the
raised exception is an instance of one of the classes whose names appear
checked in the Exception Types to Ignore list. If the exception is one of these
classes, or descends from one of these classes, the integrated debugger is not
loaded.
 If at some later time you do want the integrated debugger to load when a listed
exception is raised, either uncheck the check box next to the exception's class
name in the Exception Types to Ignore list, or select the exception class name
and click the Remove button.
 Exception Handling for Fun and Profit - exceptions are both powerful and
very misunderstood. This talk will cover exceptions from top to bottom
including their purpose, proper use, and the development of your own
exceptions.
Various tutorials and articles on using Delphi
Some exercises for you...
Since this Course is an online course, there is much you can do to prepare for the next
chapter. At the end of each chapter I'll try to provide several tasks for you to get more
familiar with Delphi and the topics we discuss in the current chapter. Here are some
suggested actions for you, after you finish reading this chapter:
• Be sure to explore Delphi Pascal Knowledge Base - the collection of tutorials and
articles explaining every aspect of programming with Delphi Pascal.
Welcome to the eighth chapter of the FREE online programming course:
A Beginner’s Guide to Delphi Programming.
Learn the art of helping yourself, when it comes to code maintenance. The purpose of
adding comments to Delphi code is to provide more program readability using
understandable description of what your code is doing
Why, How and When
Even though at *this* stage of your latest Delphi project development you are pretty
sure about what every line in the source does, you should know that after a few
months even the *simplest* functions will be hard to read - you'll simply forget what
the idea was.

Commenting code is a manner of adding (displaying) textual information in relation


to your code without it being parsed by compiler (or compiled in an exe).
The purpose of adding comments to code is to provide an understandable description
of what your code is doing - to make your source more readable - for you and for
other developers that may need to alter (or understand) the code. When you come
back to maintain the code a year later, you will thank yourself. Other users who
attempt to maintain your code will thank you as well.
There are two kinds of comments in Delphi: block comments and line comments.
Line comments (the // symbol - double-slash) were added within Delphi source, as the
name suggests, only comments out the current line. They are put at the start of the bit
of code you want to comment out and stop the compiler *seeing* any of the code after
the comment symbol and until the end of the line.
In the next example the j:=5 part of the code is commented.
var i,j :integer
...
i:=0; //j:=5
...
There are two ways to add block comments. The { } symbols and (* *) are used (in
pair) to comment out a block of code (i.e. more than one line). That is, any text
between a left brace and a right brace constitutes a comment; text between a left-
parenthesis-plus-asterisk and an asterisk-plus-right-parenthesis also constitutes a
comment. For example (from Windows.pas):
{The name immediately following SUBLANG_
dictates which primary language ID that
sublanguage ID can be combined with to
form a valid language ID.}

SUBLANG_NEUTRAL = $00; { language neutral }


{$EXTERNALSYM SUBLANG_NEUTRAL}
SUBLANG_DEFAULT = $01; { user default }
...
Notice, however, that a text that contains a dollar sign ($) immediately after the
opening { or (* (for block comments) is a compiler directive - something definitively
not ignored by the compiler. For example, {$WARNINGS OFF} tells the compiler not
to generate warning messages.
Note one more thing, if you start a comment with an open brace "{", Delphi expects
the comment to end with a closing brace "}", and not an asterisk-paren "*)".
You must be quite careful when using block comments as it often seems the case that
you could want to comment out a chunk of code that already has comments in it.
However, the problem with this is that we can't nest comments (i.e. have one set of
comments inside another) in Delphi Pascal. But, if you have a large amount of code
that you want to comment out, none of the above operators may help, depending on
whether you have already used those operators in the code. Here's a definitive way to
comment out code:
{$IFDEF False}

your code goes here


do not fear if you have used
line or block comment inside
this block!

{$ENDIF}
As a help to developer, Delphi code editor (by default) uses italics and a blue font to
specify that a peace of code is actually a comment.

Delphi Commenting TIPS


 Delphi Pascal doesn't support nesting comments of the same type within each
other.
 Each unit should begin with a comment block, that generally describes the
contents of the unit.
 Each class definition and each method (procedure/function) within a unit
should have a separate function comment, above the method. In certain
circumstances, you will use comments to document the arguments passed to a
procedure, to state whether those arguments should be within a certain range
of values, to convey whether global variables are changed within the
procedure, and to relate the procedure's return values.
 Avoid making obvious comments: i := i + 1; // Add one to i
 Misleading comments are worse than no comments at all.
 For commenting, usually { } pairs shall be used.
 The alternative notation of (* *) shall be reserved for temporarily removing
code ("commenting out") during development.
 The use of // shall be restricted to one-line comments.
 Tricky, confusing, and/or clever coding techniques should always be
documented (if not avoided entirely).
 Comments preceding functional blocks of code should be aligned with the
code on the left hand side
 When documenting source code, don't forget to update existing comments if
the code change causes the description to be inaccurate. Always make sure the
description still describes the new functionality.
 If your comments speak to how the code works, instead of to what it does, you
have created an additional code-maintenance problem, because comments that
describe how code works must be revised whenever you change the code.
 Always enter or revise your comments as soon as you write the code. Avoid
"saving it for later," because there is rarely time to do it later, or, if there is,
you will not understand the code as well when you come back to it.
Welcome to the ninth chapter of the FREE online programming course: A Beginner’s
Guide to Delphi Programming.
Yes, even the experienced Delphi programmers make mistakes
It happens to me all the time, never mind my 10+ years of Delphi programming
experience. Compiler errors like "Missing operator or semicolon" or ":= expected but
= found", after pressing the F9 key (run command in the Delphi IDE), constantly pop
up!
Suppose you are writing a recursive procedure to calculate the factorial value of an
integer. The procedure should also output each iteration value to the screen in
a console mode Delphi application.
Below is a solution to the problem - to calculate 5! (=120).
The output should look like: 1
2
6
24
120
Here's the procedure:
[line 0] procedura ErrorTesting;
[line 1] var
[line 2] l, k : integer; j : string;
[line 3] begin
[line 4] j := 1
[line 5] l := 5;
[line 6] for k:= 1 to l do
[line 7] j = k * j;
[line 8] writeln(j) ;
[line 9] end;
How many possible compile (or logic) errors (not warnings) can you count?
How many?
I'm counting four. In less than 7 lines of code there are 4 errors - and on the first look
the code looks ok!
Line 0: Error! Declaration expected but identifier 'procedura' found.
Line 4: Error! Incompatible types: 'String' and 'Integer'.
Line 5: Error! Missing operator or semicolon.
Line 7: Error! := expected but = found
There is even one logic error! If you correct all 4 errors in the source, the program
will only output one number: 120.
Errors and How to Prevent Errors
There are four types of errors a Delphi programmer can experience while developing,
even the simplest program.
Design and compile time errors are seen only by a developer, while run time and
logic errors tend to crash your program right in front of the user's eyes!
The good thing about design and compile time errors is that Delphi will not let you
build (compile) the project - therefore stopping you deploying a non working version
of the application. The bad thing about the run time and logic errors is that Delphi
will build the exe for you - but that exe might or might not work properly.
Design time and compile time errors
Fighting against these two is fairly easy.
An example. Setting the TabOrder property of any control on a form to more than
32767 will result in a design time error. A message box will pop up telling you what
the boundaries are for the property values.
On the other hand, compiler errors will prevent you from building the exe for the
application. When you press the F9 key and there are some errors in the code, Delphi
will stop the compile process and display the errors inside the Messages window.
If you know how to correct the particular error, you simply go to the line of code the
Delphi compiler reported and fix it. However, if you are unsure about some of the
errors (that is how to fix them), you should select that error inside the Messages
windows and press F1 (Help). Many times, the error description will provide enough
information for you to be able to correct your code.
In the case of ':= expected but = found', what we wanted to do is to assign the value of
the right side (k * j) of the = sign to the variable (j) on the left. By mistake, we tried to
do a logical operation '=' on the expression. Simply, changing '=' to ':=' solves the
problem.
How about 'Missing operator or semicolon'? Delphi warns that the error is in line 5.
I'm pretty sure that in
[line 5] i := 5;
there is no missing operator!
Ah, yes. We have a missing ';' at the end of line 4. But why did Delphi stated that the
error is in line 5? Remember that, in Pascal language, each line of code must end with
the ';' sign. In the code above, Delphi compiler actually sees
j := 1
i := 5;
(two lines) as (one line)
j := 1 i := 5;
Therefore, a semicolon is often missing on the previous line!
Run-time and Logic errors
Fighting against these two is, unfortunately, a fairly hard task. Firstly, you'll not know
about those errors until the users of your application report them. Secondly, you'll be
truly happy if those users succeed in explaining why your program stopped working -
in order for you to be able to recreate the situation and correct the code.
The "Preventing Access Violations in Delphi code" article provides a more in-depth
discussion on run time errors.
Logic Errors
Oh, those nasty logic errors. They can go unnoticed indefinitely, or more scarily, they
can go unnoticed indefinitely and really mess with the users data (for example, in an
accounting application).
There is a logic error in the code above - the output will only contain one line, where
we need 5 lines, for each iteration of the for loop.
The problem is in the for .. do Delphi statement. The for loop is defined as follows:
for counter := initialValue to finalValue do statement
The most tricky part is the "statement". Statement can be a simple or structured
statement that does not change the value of counter. Structured statements are built
from other statements. If the structured statement consists only of simple statements
(that is more than one) it MUST be surrounded with the begin / end pair.
You, as a beginner developer, would most likely run into problems with using the for
loop with a structured statement as if it were a simple statement.
Therefore our for loop should look like this:
for k:= 1 to l do
begin
j = k * j;
writeln(j) ;
end;
Note: ALWAYS, always write Delphi commands that expect a simple OR a structured
statement like they expect a structured statement!! In other words, if you have a for
loop with only one simple statement inside its body, DO surround that line with the
begin / end pair.
Help yourself
Spend a few bucks and buy a book! "The Tomes of Delphi Developer's Guide to
Delphi Troubleshooting", by Clay Shannon, covers all versions of Delphi. This book
is an alphabetical reference to nearly 800 design-time, compile-time, and run-time
error messages of Borland’s Delphi Object Pascal language. Error message entries
include an explanation of what actions or omissions may have caused the error
message, how to quickly resolve the problem, and how to avoid receiving the error
message in the future. Code samples clearly demonstrate how to eliminate the errors
in different versions of Delphi.
Welcome to the tenth chapter of the FREE online programming course:
A Beginner’s Guide to Delphi Programming.
Designing and developing a real game using Delphi: Tic Tac Toe.
Ok, stop playing with Delphi. After nine chapters of this course you are ready for
some real action. It's time to create a full working Delphi project, and what type of
project would you be more interested than creating a game?
Your goal in this chapter is to create a simple game using Delphi. Here are the topics
you'll practically try and learn here:
 Building a program GUI
 Building a program Logic
 Working with arrays
 Building Functions, Procedures using Parameter Passing
 Casting one Delphi object to another
Tic Tac Toe, the Rules
Tic Tac Toe is a traditional game, played by two opponents that alternately place X's
and O's into 3x3 playing field.

Before the game starts, players have to decide who will go first and who will mark his
moves with an X. After the first move, game proceeds with the opponents alternately
placing their marks at any unoccupied cell. The goal of this game is to be the first one
with three marks in a row, where the row can be horizontal, diagonal or vertical. If all
cells are occupied and both players don't have a winning combination, the game is a
draw.
Back to the "drawing board" ...
Preparing the Tic Tac Toe Game
At this moment you should have your copy of Delphi up and running. By default, as
you know by now, when you start Delphi, a new (standard Windows) project is
created hosting one Form object (and the associated unit).
Our game will require only one form where all the action is taking place, naturally
we'll use the form object Delphi "prepared" for us.
Saving for the first time
Before we move on, I suggest you to rename that default form, by assigning the string
'frMain' to the Name property (using the Object Inspector). Next, save the project, by
pointing to File | Save All (Delphi IDE main menu). The Save As dialog box will be
displayed, providing you with the option to first save the form's unit file, the default
name will be Unit1.pas. The offered location will be inside the Projects folder under
the Delphi install folder. It is advisable to save every project in a separate folder,
therefore, use the 'Create New Folder' toolbar button to create a new folder, and name
it 'TicTacToe'. Now, change the name of the Unit1.pas to Main.pas and click Save.
You will then be prompted to save the project, by default Delphi will name this
project 'Project1' (DPR), of course we'll change it to, let's say, 'TicTacToe'. That's it.
Later on, when you want to save the current status of your project, just use
Ctrl+Shift+S (keyboard shortcut for Save All).
GUI
Ok, first we build the game GUI (graphical user interface). For the moment we only
have a form object named 'frMain', use Object Inspector to change the
Captionproperty to 'About Delphi Programming: Tic Tac Toe'
The playfield. Your goal is to create a "grid" consisting of 9 cells, where each cell
will display either nothing, X or O. The simplest is to use the Label component from
the Standard palette. Drop one on the form. Note, we'll need 9 labels looking similar.
The easiest way to do that is to create first label, name it, choose the appropriate font,
color, and set every other property. For this purpose we will name the label 'lblCell0'.
Use the Object Inspector to set the following properties:
object lblCell0: TLabel // note Name = lblCell0
Left = 8
Top = 8
Width = 81
Height = 81
Alignment = taCenter
AutoSize = False
Caption = 'lblCell0'
Color = clCream
Font.Size = 16
Font.Name = 'Tahoma'
Font.Style = [fsBold]
Layout = tlCenter
end
Note: If you right click anywhere on your form, and choose 'View As Text' command
from the context pop up menu, you are presented with text description of the form’s
attributes, inside the Code Editor window. To switch back to 'For View', just right
click this "code" window and choose 'View As Form'.
After we shape up the first (cell) label, we can create other eight elements of the grid.
Select the label object, and press CTRL+C. Now, select the form (click anywhere on
the form) and press CTRL+V to create another label object. The second object will
inherit all properties from the first one, only the Name property will be re-set to
Label1. Alter it manually to lblCell1, note that you must also manually change the
Caption property to lblCell1. We do not really need the Caption property at this
moment, but if you delete it (making it an empty string) you will not be able to
differentiate one cell from another (without selecting one, of course).
Go on and create all nine labels. Make sure that the Name properties are sequential in
the grid, we will build the game logic on the cell position / name. Label object with
Name lblCell0 zero should be in the top left corner, and the bottom right label should
be lblCell8.
When you are finished your form should be looking something like:
Next, we add some more objects to the form:
 Two TButton objects (Standard Palette), name them 'btnNewGame' and
'btnResetScore',
 One TRadioGroup object (Standard Palette), name it 'rgPlayFirst', set the Caption
property to 'First to play'. Now, select this RadioGroup and point to the Items
property, click the ellipsis button. Add two radio buttons, each string in Items makes a
radio button appear in the radio group box with the string as its caption. The value of
the ItemIndex property determines which radio button is currently selected.
 One TGroupBox object (Name = 'gbScoreBoard'; Caption = 'Score Board') with
six labels (names : lblX, lblXScore, lblO, lblOScore, lblMinus, lblColon).

Once we have the game GUI, we can proceed to game logic and initialization.
The GUI design part of the Tic Tac Toe game is complete, we move on to game logic
and initialization...
Initializing the Tic Tac Toe Game
Before we start coding we have to decide how to represent the playfield (label/cell
grid) in the computer memory.
Each cell can be treated individually, we can place all cells in one-dimensional array,
or we can make two-dimensional array. Since we want to automate our program as
much as possible we will represent cells as a two dimensional array. The top-left
"corner" will have an index of [1, 1], the top-right corner will be [1, 3], the bottom-
right [3, 3], etc. Two playfield variables will be created for playfield data, as the code
will be more readable if we store each player moves separately.
Beside playfield variables we need to declare variables that will tell us how many
moves are played (iMove), who is on the move (sPlaySign), is the game in progress or
is the game over (bGameOver), and two variables that will hold the number of
victories for each player (iXScore and iOScore). Place the following code above
the implementation keyword inside the unit Main.pas, just below the frMain variable:
...
var
frMain: TfrMain; //added by Delphi - do NOT delete

iXPos : TXOPosArray;
iOPos : TXOPosArray;

sPlaySign : String;
bGameOver : Boolean;
iMove : Integer;
iXScore : Integer;
iOScore : Integer;
implementation
...
Note: the TXOPosArray (two-dimensional integer array - holding 9 elements) type is
declared above the form type declaration. Since we'll need to send an array as a
parameter to a procedure, we have to define our "array type", like:
type TXOPosArray = array [1..3, 1..3] of Integer;
Now, inside the FormCreate event handler (double click a form to create one) we add
the following code:
procedure TfrMain.FormCreate(Sender: TObject);
begin
iXScore := 0;
iOScore := 0;

InitPlayGround;
end;
The iXScore and iOScore variables are initialized when the form is created. This is
because we don't want to change their values until user presses the btnResetScore
button.
All other variables have to be initialized after the end of each game - inside the
InitPlayGround procedure.
Playfield Initialization
During a playfield initialization we will clear the cells (that is, Captions of all Labels
with lblCellX names) by setting the Caption property to '' (empty string). It is
necessary to set iXPos and iOPos arrays to zero. Beside that, we have to check the
value of the rgPlayFirst radio group ItemIndex property to determine which player
will play first in the next game. Also, we have to reset the number of moves to zero
(iMove), and set the bGameOver variable to False.
To create the InitPlayGround procedure, go to the Code Editor window and below the
implementation keyword, add the next code:
procedure TfrMain.InitPlayGround;
var
i, j, k: integer;
begin
for i := 1 to 3 do
begin
for j := 1 To 3 do
begin
k:= (i - 1) * 3 + j - 1; // 0 .. 8
TLabel(FindComponent('lblCell' + IntToStr(k))).Caption := '';
iXPos[i, j] := 0;
iOPos[i][j] := 0;
end;
end;

if rgPlayFirst.ItemIndex = 0 then sPlaySign := 'X';


if rgPlayFirst.ItemIndex = 1 then sPlaySign := 'O';

bGameOver := False;
iMove := 0;
end;
Note that you will also need to add this procedure's header in the private section of the
form declaration (interface part of the unit):
...
private
procedure InitPlayGround;
...
Stop for the moment, and consider the next two lines in the above code:
k:= (i - 1) * 3 + j - 1; // 0 .. 8
TLabel(FindComponent('lblCell' + IntToStr(k))).Caption := '';
What we want to do in the InitPlayGround procedure is to set the caption to all
lblCellX label components to an empty string. One way to achieve this is to use 9
lines of code:
lblCell0.Caption:='';
lblCell1.Caption:='';
lblCell2.Caption:='';
...
lblCell7.Caption:='';
lblCell8.Caption:='';
Bad practice! The above code "tip", searches for a component on a form by its name
(FindComponent), casts it to TLabel and assigns an empty string to the Caption
Property. FindComponent returns the component in the forms Components property
array with the name that matches the string in the only parameter required.
Until now we have created the Tic Tac Toe game GUI and we've initialized the
variables. We are ready to make our first move.
X, your turn! (or O if you prefer)
Event OnClick for the particular lblCellX component will be called each time a player
presses a (left) mouse button on the Label component making the playfield grid.
There is one more thing we have to check before we process the user input. It is very
important to be sure that the current game is in progress. In the case that the game is
over, we will simply exit this procedure.
After we verify that the game is in progress, we can process the user input - in a
procedure GamePlay that expect an integer parameter named CellIndex. CellIndex
would have the value set to 0 for the top-left corner cell (label) of the playfield and
the value 8 for the bottom-right corner. To get the CellIndex we extract the cell
number from the label name, for example, if the label clicked is named lblCell5, the
CellIndex for that label (cell) is 5.
To create an OnClick event handler procedure for the first label (lblCell0), simply
double click it, or select the lblCell0 component, go to Object Inspector, switch to
Events and double click the column right to OnClick. Either way, the Code Editor
receives the input focus, and you can enter the programming logic code for the
OnClick handler:
procedure TfrMain.lblCell0Click(Sender: TObject);
var
iWin : integer;
CellIndex : 0..8;
begin
if bGameOver = True Then Exit;
if TLabel(Sender).Caption <> '' then
begin
ShowMessage('Cell occupied!');
Exit;
end;
CellIndex := StrToInt(RightStr(TLabel(Sender).Name,1));
iWin := GamePlay(CellIndex);
end;
Note 0: If the cell is occupied (was clicked before during the game), we exit the
procedure. In order to "see" whether the cell is occupied we use the Sender parameter,
cast it to TLabel and test the Caption property. If the cell already hosts 'X' or 'O', we
display a message using the ShowMessage Delphi function.
Note 1 : to assign a value to CellIndex we use the RightStr function defined in the
StrUtils.pas unit. You'll need to manually add this unit to the uses list in the interface
uses clause of the Main.pas unit. If you forget to do that, the next time you try to
compile your project, thecompiler will report an error: "Undeclared Identifier
'RightStr'"!!
Note 2: The above event handler was coded with the following in mind: there is no
need to have a separate event handler for each and every of 9 cells making the
playfield. What we are preparing for, with the above code, is to use this one procedure
for all lblCellX label components and their OnClick events. To have the lblCellX
(where X stands for 0 to 8) share this specific OnClick event handler, do the
following:
a) select the "rest" of the eight lblCell label components (Shift + Click 'em);
b) Go the Events page of the Object Inspector
c) In the right column to the OnCLick, from the drop down list pick the lblCell0Click.
If you experience difficulties, check the How To Share an Event Handler in Delphi.
Processing the Move
Before we start with processing, let me refresh your memory. iXPos and iOPos arrays
hold information about already played moves by the X and O player. iMove tells us
how many moves are already made.
The user's choice must be the regular move, which means that user didn't click on the
already occupied cell.
If the user makes a regular move, we have to increase the counter (iMove) by one.
Then we have to translate the coordinates form one-dimensional to two-dimensional
array. The xo_Move value of 0 will be translated to [1,1], 1 to [1,2] ... 3 to [2,1] ... 8 to
[3,3].
function TfrMain.GamePlay(xo_Move : Integer):integer;
var
x, y : 1..3;
iWin : integer;
begin
Result := -1;

Inc(iMove);
x := (xo_Move Div 3) + 1;
y := (xo_Move Mod 3) + 1;
...
Also we have to check which player has made the move because this routine will
process the input from both players.
if sPlaySign = 'O' then
begin
iOPos[x,y] := 1;
iWin := CheckWin(iOPos);
end
else
begin
iXPos[x,y] := 1;
iWin := CheckWin(iXPos);
end;

TLabel(FindComponent('lblCell' + IntToStr(xo_Move))).Caption := sPlaySign;


For example, when the player X places his mark in the top left corner of the playfield,
the variables will have the following values:

Take a look at the values when O player makes his move in the middle of the
playfield:

After every move we call the CheckWin function to look for the winning
combination.
If the player succeeds to win, CheckWin will return the number of the winning
combination. If there is no winner, CheckWin will return -1. In case that we have the
winner the game will end, the current result will be placed on the scoreboard, and a
congratulation message will pop up.
Result := iWin;

if iWin >= 0 then


begin
bGameOver := True;
//mark victory

if sPlaySign = 'X' then


begin
iXScore := iXScore + 1;
lblXScore.Caption := IntToStr(iXScore);
end
else
begin
iOScore := iOScore + 1;
lblOScore.Caption := IntToStr(iOScore);
end;

ShowMessage(sPlaySign + ' - Wins!');


end;
If players played nine moves and there is no winner, it is a draw; and the current game
is over. On the other hand, if the game is still in progress, we have to allow the other
player to make his move.
if (iMove = 9) AND (bGameOver = False) Then
begin
ShowMessage('It''s a Draw!');
bGameOver := True
end;

if sPlaySign = 'O' Then


sPlaySign := 'X'
else
sPlaySign := 'O';
end; //function GamePlay
Ok, we now have the main part of the code logic done, what's left is the CheckWin
function.
Do we have a winner?
After each move we have to check if the game is over. Game could end in three ways:
X player wins the game, O player wins the game, or it is a draw. To look for a
possible winner we count the number of X's and O's in each row, column and
diagonal. If the player manages to put three signs in row, column or diagonal, we have
the winner.
Here's a portion of the CheckWin function, for the rest of the function look at
the project code.
function TfrMain.CheckWin(iPos : TXOPosArray) : Integer;
var
iScore : Integer;
i : Integer;
j : Integer;
begin
Result := -1;

//in rows?
iScore := 0;
for i := 1 to 3 do
begin
iScore := 0;
Inc(Result);
for j := 1 To 3 do Inc(iScore, iPos[i,j]);
if iScore = 3 Then Exit
end;//for i
...
Note that you will have to add these procedure's headers in the private section of the
form declaration, as you did with the InitPlayGround procedure. Now the private
section is looking like:
private
procedure InitPlayGround;
function GamePlay(xo_Move : Integer) : integer;
function CheckWin(iPos : TXOPosArray) : integer;
We now move to the last stage of our Tic Tac Toe development, the creation of the
event handlers for 'New Game' and 'Reset Score'...
There are two more handlers we have to code: create a procedure for a new game, and
a procedure that will reset the score.
New Game
We already have the InitPlayGround procedure, therefore creating a new game would
be a peace of cake. All we have to do is call the InitPlayGround.
Note: a player can click on the btnNewGame button after the game is finished
(bGameOver = True) or during a game (bGameOver = False). Because of that it
would be nice to ask the user to confirm his decision.
In this particular case there would be negligible damage or there would be no damage
at all. Just remember that good programming practice implicate getting the user's
confirmation.
Double click the btnNewGame button to create and add the code for the New Game
option:
procedure TfrMain.btnNewGameClick(Sender:
TObject);
begin
if bGameOver = False then
begin
if MessageDlg(
'End the current game?',
mtConfirmation,
mbOKCancel,0) = mrCancel then Exit;
end;

InitPlayGround;
end;
Note: if you are using a Non English Delphi version and want to have dialog buttons
and messages on your local language, explore the article: "The fastest path to Delphi
localization".
Reseting the Scoreboard
Recall that the iXScore and iOScore variables hold the number of wins for each
player. Oponents read the score form the lblXScore and lblOScore labels.
The task is simple: set iXScore and iOScore values to zero, and update
lblXScore.Caption and lblOScore.Caption.
Off course, get the user's confirmation first.
procedure TfrMain.btnResetScoreClick(Sender: TObject);
begin
if MessageDlg(
'Reset the scores?',
mtConfirmation,
mbOKCancel,0) = mrCancel then Exit;

iXScore := 0;
iOScore := 0;
lblXScore.Caption := IntToStr(iXScore);
lblOScore.Caption := IntToStr(iOScore);
end;

Some exercises for you...


Please, take a look at that CheckWin function again. Note that in case of a "we have a
winner" it returns an integer number from 0 to 8. What is this number for? See the
picture:
If we have a winner and the winner has occupied the middle row, the CheckWin will
retun 2.
Your excersise (for this chapter) is to create a new function to mark (in any way you
want) a victory on a playfied. For example, you could change the background color
for the three labels making the wining combination. Or, draw an ellipse arround the
cells, it's up to you... ;)
Welcome to the eleventh chapter of the FREE online programming course:
A Beginner’s Guide to Delphi Programming.
Learn how to create a "multiple document interface" application using Delphi.
It's time for your second Delphi project! This time we'll explore building MDI
applications using Delphi. After you have created your first Delphi game, you could
start developing your own set of Delphi games... Then, why not create one "big"
Delphi application to host all your games!
Multiple Document Interface Applications
In simple words, in an MDI application, more than one document or child window
can be opened within a single parent window. This is a common situation in
applications such as spreadsheets or word processors - one window, usually called the
MDI parent or MDI container, contains many other windows, usually called child
forms.
In our scenario, MDI parent form would be the host to all your games developed as
single form (window) MDI child forms.
To start working with MDI interfaces using Delphi, first read the "MDI Development
in Delphi" article. Inside the article you'll first find the definition of an MDI
application. The article then describes constructing multiple document interface
application with Delphi. It also provides a discussion on MDI parent/child
relationship, menus and some most important MDI parent form properties.
When you a ready, you can proceed to "Creating a multiple document interface
graphic file viewer". This article extends the topics described in the first part - letting
you expand your MDI application with some real code to open and display most
commonly used graphic files (pictures).
Extending your MDI development knowledge
Ok. By now (I suppose you have read the above two articles!) you should be familiar
with the key topics involved in developing MDI applications using Delphi.
Another task, you might be interested in, is how to store an MDI child in a DLL.
Now, I'll provide two more code samples you could find useful in an MDI application.
• Create New or Show Already Created Child Form?
Assume, for the minute, that you have an MDI application with more than one type of
child forms. What I mean, is that you can have your Delphi MDI parent form host one
child form designed to be able to display graphic files (TGraphicChildForm) and
another form (TMemoChildForm) designed to display some text inside, for example,
a Memo component. Now, think of a situation when you have more than a dozen of
various types of MDI child forms.
What you might want to have in your application, is the ability to decide whether a
new instance of the specified child form is to be created or the previous instance
should be brought to front - if one exists.
The following code snippet solves the above "problem":
procedure TMainForm.CreateMDIChild(frm : TFormClass);
const
sMDIChildFormCreated =
'Form "%s" already created. Select OK to
switch to it, Yes to create a new form instance.';
var
i:integer;
bCreated:boolean;
f:TForm;
begin
bCreated:=False;
for i:= 0 to MainForm.MDIChildCount-1 do
begin
if MainForm.MDIChildren[i] is frm then
begin
f:=MainForm.MDIChildren[i] as frm ;
bCreated:=True;
Break;
end;
end;

if not bCreated then


begin
frm.Create(Application);
end
else
begin
case MessageDlg(Format(sMDIChildFormCreated,
[f.caption]), mtConfirmation,
[mbOk, mbYes, mbCancel],0) of
mrOK: f.Show;
mrYes: frm.Create(Application);
mrCancel: //do notingg
end; //case
end;
end;
To call the above procedure you can use the following code:
CreateMDIChild(TGraphicChildForm);
//or
CreateMDIChild(TMemoChildForm);
Let's see what the above code does... First, you send to the CreateMDIChild
procedure the name of the child form class as the parameter. Then, in the
CreateMDIChild procedure, the code looks for an already created MDI child form of
the provided class, if the one is not found, one gets created. If, on the other hand, the
instance (at least one) of the child form is already created, the message dialog asks the
user whether to create a new instance or to bring to front that previously created form.
Again, more or less a not_for_beginner type of code, but you should be able to
understand the way it works.
• Hide MDI child form when minimized
As you know by now, when you try to close an MDI child form, the form only gets
minimized. You are however, able to set the Action parameter of the OnClose event
method to caFree to really close it; or even set to caNone - to "mark" that the form
cannot be closed.
procedure TMDIChild.FormClose
(Sender: TObject; var Action: TCloseAction);
begin
Action:=caNone;
end;
However, as stated, the default behavior for MDI child form OnClose event is to
become minimized. Now, if you want to hide the form when it gets minimized you
can use the next trick:
procedure TMDIChild.WMSize(var M:TWMSIZE);
begin
if M.SizeType=Size_Minimized then
ShowWindow(Handle,Sw_Hide);
end;
Note 1: You'll need to add the procedure header to the private part of the form type
declaration:
private
procedure WMSize(var M : TWMSIZE); Message WM_Size;
Note 2: Even though this procedure relies on Windows Messages and handling them,
don't be afraid to use it. The truth is that you are *beginner* developer, but you are
trying to become an expert ;)
Some exercises for you...
As in all the chapters of this course, I'll provide an idea for you to try to use the
gathered knowledge in a real situation. This is what you could do: build an MDI
application and make your Delphi TicTacToe game from the previous chapter to be
the child form of the created MDI project.

S-ar putea să vă placă și