Getting Started With Delphi
Getting Started With Delphi
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.
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.
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}
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)
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;
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.
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:
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;
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 :)
...
...
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
procedure MyProc2;
begin
MyProc1(5) ;
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;
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 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....
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.
{$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.
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;
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;
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;
//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;