You are on page 1of 63

Writing your first Delphi program

Different types of application


Delphi allows you to create GUI (Graphical User Interface) or Console (text-only)
applications (programs) along with many other types. We will concern ourselves here
with the common, modern, GUI application.

Delphi does a lot of work for us - the programmer simply uses the mouse to click,
drag, size and position graphical parts to build each screen of the application.

Each part (or element) can be passive (displaying text or graphics), or active
(responding to a user mouse or keyboard action).

This is best illustrated with a very simple program.

Creating a simple 'Hello World' program


When you first run Delphi, it will prepare on screen a new graphical application. This
comprises a number of windows, including the menu bar, a code editor, and the first
screen (form) of our program. Do not worry about the editor window at the moment.

The form should look something like this :

We have shown the form reduced in size for convenience here, but you will find it
larger on your computer. It is a blank form, onto which we can add various controls
and information. The menu window has a row of graphical items that you can add to
the form. They are in tabbed groups : Standard, Additional, Win32 and so on.

We will select the simplest from the Standard collection. Click on the A image to
select a Label. This A will then show as selected:

Having selected a graphical element, we then mark out on the form where we want
to place the element. This is done by clicking and dragging. This gives us our first
form element:

Changing graphical element properties


Notice that the graphical element contains the text Label1 as well as resize corners.
The text is called the Caption, and will appear when we run the application. This
Caption is called a Property of the button. The label has many other properties
such as height and width, but for now, we are only concerned with the caption.

Let us blank out the caption. We do this in the window called the Object Inspector
(available under the View menu item if not already present):
Writing a more meaningful program
end;

end.
The displayed file before correction
Teh cat sat on eth mat
The cat did not sit on eth mat or teh floor

Teh teh teh eth eth eht eht


Final line of 5.
The displayed file after correction
The cat sat on the mat
The cat did not sit on the mat or the floor

The the the the the the the


Final line of 5.
The displayed statistics
Teh/teh changed on 5 lines
eth changed on 4 lines
eht changed on 2 lines
The file has 5 lines
Improving the second tutorial program
changeCounts[EHT] := fullText.Replace('eht','the');

// Store the changed text back into the string list


fileData.Text := fullText.Text;

// And redisplay this string list


MemoBox.Text := fileData.Text;

// Display the word change totals


if changeCounts[TEH] = 1
then Label1.Caption := 'Teh/teh changed once'
else Label1.Caption := 'Teh/teh changed '+
IntToStr(changeCounts[TEH])+' times';

if changeCounts[ETH] = 1
then Label2.Caption := 'eth changed once'
else Label2.Caption := 'eth changed '+
IntToStr(changeCounts[ETH])+' times';

if changeCounts[EHT] = 1
then Label3.Caption := 'eht changed once'
else Label3.Caption := 'eht changed '+
IntToStr(changeCounts[EHT])+' times';

// Finally, display the number of words in the file


Label4.Caption := 'There are '+IntToStr(fullText.WordCount)+
' words in the file';

// Finally, indicate that the file is now eligible for saving


SaveButton.Enabled := true;

// And that no more corrections are necessary


CorrectButton.Enabled := false;

// Finally, free the TStringy object


fullText.Free;
end;

end.
The displayed file before correction
Teh cat
sat on
eth mat
The cat
did not
sit on
eth mat
or teh
floor

Teh teh
teh eth
eth eht
eht
Final
line of
5.
The displayed file after correction
The cat
sat on
the mat
The cat
did not
sit on
the mat
or the
floor

The the
the the
the the
the
Final
line of
5.
The displayed statistics
Teh/teh
Delphi data types

Storing data in computer programs


For those new to computer programming, data and code go hand in hand. You cannot write a program of any real
value without lines of code, or without data. A Word Processor program has logic that takes what the user types
and stores it in data. It also uses data to control how it stores and formats what the user types and clicks.

Data is stored in the memory of the computer when the program runs (it can also be stored in a file, but that is
another matter beyond the scope of this tutorial). Each memory 'slot' is identified by a name that the programmer
chooses. For example LineTotal might be used to name a memory slot that holds the total number of lines in a
Word Processor document.

The program can freely read from and write to this memory slot. This kind of data is called a Variable. It can
contain data such as a number or text. Sometimes, we may have data that we do not want to change. For
example, the maximum number of lines that the Word Processor can handle. When we give a name to such data,
we also give it its permanent value. These are called constants.

Simple Delphi data types


Like many modern languages, Delphi provides a rich variety of ways of storing data. We'll cover the basic, simple
types here. Before we do, we'll show how to define a variable to Delphi:

var // This starts a section of variables


LineTotal : Integer; // This defines an Integer variable called LineTotal
First,Second : String; // This defines two variables to hold strings of text
We'll show later exactly where this var section fits into your program. Notice that the variable definitions are
indented - this makes the code easier to read - indicating that they are part of the var block.

Each variable starts with the name you choose, followed by a : and then the variable type. As with all Delphi
statements, a ; terminates the line. As you can see, you can define multiple variables in one line if they are of the
same type.

It is very important that the name you choose for each variable is unique, otherwise Delphi will not know how to
identify which you are referring to. It must also be different from the Delphi language keywords. You'll know when
you have got it right when Delphi compiles your code OK (by hitting Ctrl-F9 to compile).

Delphui is not sensitive about the case (lower or upper) of your names. It treats theCAT name the same as
TheCat.

Number types
Delphi provides many different data types for storing numbers. Your choice depends on the data you want to
handle. Our Word Processor line count is an unsigned Integer, so we might choose Word which can hold values
up to 65,535. Financial or mathematical calculations may require numbers with decimal places - floating point
numbers.

var
// Integer data types :
Int1 : Byte; // 0 to 255
Int2 : ShortInt; // -127 to 127
Int3 : Word; // 0 to 65,535
Int4 : SmallInt; // -32,768 to 32,767
Int5 : LongWord; // 0 to 4,294,967,295
Int6 : Cardinal; // 0 to 4,294,967,295
Int7 : LongInt; // -2,147,483,648 to 2,147,483,647
Int8 : Integer; // -2,147,483,648 to 2,147,483,647
Int9 : Int64; // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

// Decimal data types :


Dec1 : Single; // 7 significant digits, exponent -38 to +38
Dec2 : Currency; // 50+ significant digits, fixed 4 decimal places
Dec3 : Double; // 15 significant digits, exponent -308 to +308
Dec4 : Extended; // 19 significant digits, exponent -4932 to +4932
Some simple numerical variable useage examples are given below - fuller details on numbers is given in the
Numbers tutorial.

Text types
Like many other languages, Delphi allows you to store letters, words, and sentences in single variables. These
can be used to display, to hold user details and so on. A letter is stored in a single character variable type, such
as Char, and words and sentences stored in string types, such as String.

var
Str1 : Char; // Holds a single character, small alphabet
Str2 : WideChar; // Holds a single character, International alphabet
Str3 : AnsiChar; // Holds a single character, small alphabet
Str4 : ShortString; // Holds a string of up to 255 Char's
Str5 : String; // Holds strings of Char's of any size desired
Str6 : AnsiString; // Holds strings of AnsiChar's any size desired
Str7 : WideString; // Holds strings of WideChar's of any size desired
Integer and floating point numbers
The different number types in Delphi
Delphi provides many different data types for storing numbers. Your choice depends on the data you want to handle. In general,
smaller number capacities mean smaller variable sizes, and faster calculations. Ideally, you should use a type that comfortably
copes with all possible values of the data it will store.

For example, a Byte type can comfortably hold the age of a person - no-one to date has lived as long as 255 years.

With decimal numbers, the smaller capacity types also have less precision. Less numbers of significant digits. Let us look at the
different types:

Type Storage size Range

Byte 1 0 to 255
ShortInt 1 -127 to 127
Word 2 0 to 65,535
SmallInt 2 -32,768 to 32,767
LongWord 4 0 to 4,294,967,295
Cardinal 4* 0 to 4,294,967,295
LongInt 4 -2,147,483,648 to 2,147,483,647
Integer 4* -2,147,483,648 to 2,147,483,647
Int64 8 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Single 4 7 significant digits, exponent -38 to +38


Currency 8 50+ significant digits, fixed 4 decimal places
Double 8 15 significant digits, exponent -308 to +308
Extended 10 19 significant digits, exponent -4932 to +4932

* Note : the Integer and Cardinal types are both 4 bytes in size at present (Delphi release
7), but are not guaranteed to be this size in the future. All other type sizes are guaranteed.

Assigning to and from number variables


Number variables can be assigned from constants, other numeric variables, and expressions:

const
YOUNG_AGE = 23; // Small integer constant
MANY = 300; // Bigger integer constant
RICH = 100000.00; // Decimal number : note no thousand commas

var
Age : Byte; // Smallest positive integer type
Books : SmallInt; // Bigger signed integer
Salary : Currency; // Decimal used to hold financial amounts
Expenses : Currency;
TakeHome : Currency;

begin
Age := YOUNG_AGE; // Assign from a predefined constant
Books := MANY + 45; // Assign from a mix of constants (expression)
Salary := RICH; // Assign from a predefined constant
Expenses := 12345.67; // Assign from a literal constant
TakeHome := Salary; // Assign from another variable
TakeHome := TakeHome - Expenses; // Assign from an expression
end;
Age is set to 23
Books is set to 345
Salary is set to 100000.00
Expenses is set to 12345.67
TakeHome is set to 87654.33

Numerical operators
Number calculations, or expressions, have a number of primitive operators available:

+ : Add one number to another


- : Subtract one number from another
* : Multiply two numbers
/ : Divide one decimal number by another
div : Divide one integer number by another
mod : Remainder from dividing one integer by another
When using these multiple operators in one expression, you should use round brackets to wrap around sub-expressions to ensure
that the result is obtained. This is illustrated in the examples below:

var
myInt : Integer; // Define integer and decimal variables
myDec : Single;
Strings and characters
alphabets of China, Japan and so on. These are called International characters. International applications must
use WideChar and WideString types.

Strings
A single character is useful when parsing text, one character at a time. However, to handle words and sentences
and screen labels and so on, strings are used. A string is literally a string of characters. It can be a string of Char,
AnsiChar or WideChar characters.

Assigning to and from a string


A ShortString is a fixed 255 characters long. A String (by default) is the same as an AnsiString, and is of
any length you want. WideStrings can also be of any length. Their storage is dynamically handled. In fact, if
you copy one string to another, the second will just point to the contents of the first.

Here are some assignments:

var
source, target, last : String;
begin
source := 'Hello World'; // Assign from a string literal
target := source; // Assign from another variable
last := 'Don''t do that'; // Quotes in a string must be doubled
end;
source is now set to : Hello World
target is now set to : Hello World
last is now set to : Don't do that
String operators
There are a number of primitive string operators that are commonly used:

+ Concatenates two strings together


= Compares for string equality
< Is one string lower in sequence than another
<= Is one string lower or equal in sequence with another
> Is one string greater in sequence than another
>= Is one string greater or equal in sequence with another
<> Compares for string inequality
Here are some examples using these operators:

var
myString : string;
begin
myString := 'Hello ' + 'World'; // String concatenation

if 'ABC' = 'abc' // Equality


then ShowMessage('ABC = abc');
if 'ABC' = 'ABC' // Equality
then ShowMessage('ABC = ABC');
if 'ABC' < 'abc' // Less than
then ShowMessage('ABC < abc');
if 'ABC' <= 'abc' // Less than or equal
then ShowMessage('ABC <= abc');
if 'ABC' > 'abc' // Greater than
then ShowMessage('ABC > abc');
if 'ABC' >= 'abc' // Greater than or equal
then ShowMessage('ABC >= abc');
if 'ABC' <> 'abc' // Inequality
then ShowMessage('ABC <> abc');
end;
ABC = ABC
ABC < abc
ABC <= abc
ABC <> abc
String processing routines
There are a number of string manipulation routines that are given by example below. Click on any of them to
learn more (and also click on WrapText for another, more involved routine).

var
Source, Target : string;

begin
Source := '12345678';
Target := Copy(Source, 3, 4); // Target now = '3456'

Target := '12345678';
Insert('-+-', Target, 3); // Target now = '12-+-345678'
Enumerations, SubRanges and Sets
Enumerations
The provision of enumerations is a big plus for Delphi. They make for readable and reliable code. An enumeration is
simply a fixed range of named values. For example, the Boolean data type is itself an enumeration, with two
possible values : True and False. If you try to assign a different value to a boolean variable, the code will not
compile.

Defining enumerations
When you want to use an enumeration variable, you must define the range of possible values in an
enumeration type first (or use an existing enumeration type, such as boolean). Here is an example:

type
TSuit = (Hearts, Diamonds, Clubs, Spades); // Defines enumeration range
var
suit : TSuit; // Defines enumeration variable
begin
suit := Clubs; // Set to one of the values
end;
The TSuit type definition creates a new Delphi data type that we can use as a type for any new variable in our
program. (If you define types that you will use many times, you can place them in a Unit file and refer to this
in a uses statement in any program that wants to use them). We have defined an enumeration range of names
that represent the suits of playing cards.

We have also defined a suit variable of that TSuit type, and have assigned one of these values. Note that there
are no quote marks around these enumeration values - they are not strings, and they take no storage.

In fact, each of the enumeration values is equated with a number. The TSuit enumeration will have the
following values assigned :

Hearts = 0 , Diamonds = 1 , Clubs = 2 , Spades = 3

And you can use these values instead of the enumeration values, although this loses many of the benefits of
enumerations. You can even override the default number assignments:

type
TSuit = (Hearts=13, Diamonds, Clubs=22, Spades);
var
suit : TSuit;
begin
suit := Clubs;
end;
The enumeration values assigned are now :

Hearts = 13 , Diamonds = 14 , Clubs = 22 , Spades = 23

Using enumeration numbers


Since enumeration variables and values can also be treated as numbers (ordinals), we can use them in
expressions :

type
TDay = (Mon=1, Tue, Wed, Thu, Fri, Sat, Sun); // Enumeration values
var
today : TDay;
weekend : Boolean;
begin
today := Wed; // Set today to be Wednesday

if today > Fri // Ask if it is a weekend day


then weekend := true
else weekend := false;
end;
today is set to Wed which has ordinal value = 3
weekend is set to false since Wed (3) <= Fri (5)
And we can also use them in loops (for more on looping, see the Looping tutorial). Here is an example :

type
TDay = (Mon=1, Tue, Wed, Thu, Fri, Sat, Sun); // Enumeration values
var
day : TDay; // Enumeration variable
begin
for day := Mon to Fri do
begin
// day has each of the values Mon to Fri ( 1 to 5) in 5 iterations
// of this loop, allowing you to whatever you want.
end;
end;
Holding sets of data
About arrays
Arrays are ordered collections of data of one type. Each data item is called an element, and is accessed by its
position (index) in the array. They are very useful for storing lists of data, such as customers, or lines of text.

There are a number of types of array, and array may be single or multidimensional (lists of lists in effect).

Constant arrays
It is probably easiest to introduce arrays that are used to hold fixed, unchangeable information. Constant arrays.
These can be defined in two kinds of ways:

const
Days : array[1..7] of string = ('Mon','Tue','Wed','Thu','Fri','Sat','Sun');
This first way declares the array as well as the contents in one statement. Alternatively:

type
TDays = array[1..7] of string;
const
Days : TDays = ('Mon','Tue','Wed','Thu','Fri','Sat','Sun');
In both cases, we have defined an array of constants that represent the days of the week. We can use them by day
number:

const
Days : array[1..7] of string = ('Mon','Tue','Wed','Thu','Fri','Sat','Sun');
var
i : Integer;
begin
for i := 1 to 5 do // Show the weekdays
ShowMessageFmt('Day %d = %s',[i,Days[i]]);
end;
The ShowMessageFmt routine displays our data - click on it to learn more.
The data displayed by the above code is:

Day 1 = Mon
Day 2 = Tue
Day 3 = Wed
Day 4 = Thu
Day 5 = Fri

Different ways of defining array sizes


The Days array above was defined with a fixed 1..7 dimension. Such an array is indexable by values 1 to 7. We
could have used other ways of defining the index range:

Using enumerations and subranges to define an array size


SubRanges are covered in the Enumerations and sets tutorial. Below, we define an enumeration, then a
subrange of this enumeration, and define two arrays using these.

type
TCars = (Ford, Vauxhall, GM, Nissan, Toyota, Honda);
var
cars : array[TCars] of string; // Range is 0..5
japCars : array[Nissan..Honda] of string; // Range is 3..5
begin
// We must use the appropriate enumeration value to index our arrays:
japCars[Nissan] := 'Bluebird'; // Allowed
japCars[4] := 'Corolla'; // Not allowed
japCars[Ford] := 'Galaxy'; // Not allowed
end;
Note that the cars array is defined using just a data type - TCars. The size and range of the data type
dictates how we use the array.

Using a data type


If we had used Byte as the array size, our array would be the size of a byte - 256 elements - and start with
the lowest byte value - 0. We can use any ordinal data type as the definition, but larger ones, such as Word
make for large arrays!

Static arrays
There are other ways that arrays vary. Static arrays are the easiest to understand, and have been covered so far.
They require the size to be defined as part of the array definition. They are called static because their size is static,
and because they use static memory.

Dynamic arrays
Dynamic arrays do not have their size defined in their declaration:
Storing groups of data together
What are records?
Records are a useful and distinguishing feature of delphi. They provide a very neat way of having named data structures -
groups of data fields. Unlike arrays, a record may contain different types of data.

Records are fixed in size - the definition of a record must contain fixed length fields. We are allowed to have strings, but
either their length must be specified (for example a : String[20]), or a pointer to the string is stored in the record. In this
case, the record cannot be used to write the string to a file. The TPoint type is an example of a record. Before we go any
further, let us look at a simple example.

type
TCustomer = record
name : string[30];
age : byte;
end;

var
customer : TCustomer;

begin
// Set up our customer record
customer.name := 'Fred Bloggs';
customer.age := 23;
end;
When we define a record, each field is simply accessed by name, separated by a dot from the record variable name. This
makes records almost self documenting, and certainly easy to understand.

Above, we have created one customer, and set up the customer record fields.

Using the with keyword


When we are dealing with large records, we can avoid the need to type the record variable name. This avoidance, however,
is at a price - it can make the code more difficult to read:

type
TCustomer = record
name : string[30];
age : byte;
end;

var
John, Nancy : TCustomer;

begin
// Set up our customer records
with John do
begin
name := 'John Moffatt'; // Only refer to the record fields
age := 67;
end;

with Nancy do
begin
name := 'Nancy Moffatt'; // Only refer to the record fields
age := 77;
end;
end;

A more complex example


In practice, records are often more complex. Additionally, we may also have a lot of them, and might store them in an
array. The following example is a complete program that you may copy and paste into your Delphi product, making sure to
follow the instructions at the start of the code.

Please note that this is quite a complex piece of code - it uses a procedure that takes a variable number of parameters,
specially passed in square brackets (see Procedure for more on procedures).

// Full Unit code.


// -----------------------------------------------------------
// You must store this code in a unit called Unit1 with a form
// called Form1 that has an OnCreate event called FormCreate.

unit Unit1;

interface

uses
Forms, Dialogs;
Programming logic
What is programming logic?
Programming in Delphi or any other language would not work without logic. Logic is the glue that holds together
the code, and controls how it is executed. For example, supposing we were writing a word procesor program. When
the user presses the Enter key, we will move the cursor to a new line. The code would have a logical test for the
user hitting the Enter key. If hit we do a line throw, if not, we continue on the same line.

If then else
In the above example, we might well use the If statement to check for the Enter key.

Simple if then else


Here is an example of how the if statement works:

var
number : Integer;
text : String;
begin
number := Sqr(17); // Calculate the square of 17
if number > 400
then text := '17 squared > 400' // Action when if condition is true
else text := '17 squared <= 400'; // Action when if condition is false
end;
text is set to : '17 squared <= 400'
There are a number of things to note about the if statement. First that it spans a few lines - remember that
Delphi allows statements to span lines - this is why it insists on a terminating ;

Second, that the then statement does not have a terminating ; -this is because it is part of the if statement,
which is finished at the end of the else clause.

Third, that we have set the value of a text string when the If condition is successful - the Then clause - and
when unsuccessful - the Else clause. We could have just done a then assignment:

if number > 400


then text := '17 squared > 400';
Note that here, the then condition is not executed (because 17 squared is not > 400), but there is no else
clause. This means that the if statement simply finishes without doing anything.

Note also that the then clause now has a terminating ; to signify the end of the if statement.

Compound if conditions, and multiple statements


We can have multiple conditions for the if condition. And we can have more than one statement for the then
and else clauses. Here are some examples:

if (condition1) And (condition2) // Both conditions must be satisfied


then
begin
statement1;
statement2;
...
end // Notice no terminating ';' - still part of 'if'
else
begin
statement3;
statement4;
...
end;
We used And to join the if conditions together - both must be satisfied for the then clause to execute.
Otherwise, the else clause will execute. We could have used a number of different logical primitives, of which
And is one, covered under logical primitives below.

Nested if statements
There is nothing to stop you using if statements as the statement of an if statement. Nesting can be useful,
and is often used like this:

if condition1
then statement1
else if condition2
then statement2
else statement3;
However, too many nested if statements can make the code confusing. The Case statement, discussed below,
can be used to overcome a lot of these problems.

Logicial primitives
Before we introduce these, it is appropriate to introduce the Boolean data type. It is an enumerated type, that can
Repeating sets of commands
Why loops are used in programming
One of the main reasons for using computers is to save the tedium of many repetitive tasks. One of the main uses of loops in
programs is to carrry out such repetitive tasks. A loop will execute one or more lines of code (statements) as many times as you
want.

Your choice of loop type depends on how you want to control and terminate the looping.

The For loop


This is the most common loop type. For loops are executed a fixed number of times, determined by a count. They terminate when
the count is exhausted. The count (loop) is held in a variable that can be used in the loop. The count can proceed upwards or
downwards, but always does so by a value of 1 unit. This count variable can be a number or even an enumeration.

Counting up
Here is a simple example counting up using numeric values:

var
count : Integer;
begin
For count := 1 to 5 do
ShowMessageFmt('Count is now %d',[count]);
end;
Count is now 1
Count is now 2
Count is now 3
Count is now 4
Count is now 5
The ShowMessageFmt routine is useful for displaying information - click on it to read more.

Counting up using an enumeration


Enumerations (see Enumeration and sets to explore) are very readable ways of assigning values to variables by name. They can
also be used to control For loops:

type
TWeekDay = (Monday=1, Tuesday, Wednesday, Thursday, Friday);
var
weekday : TWeekDay;
hours : array[TWeekDay] of byte;
begin
// Set up the hours every day to zero
for weekDay := Monday to Friday do
hours[weekDay] := 0;

// Add an hour of overtime to the working hours on Tuesday to Thursday


for weekDay := Tuesday to Thursday do
Inc(hours[weekDay]);
end;
hours[Monday] = 0
hours[Tuesday] = 1
hours[Wednesday] = 1
hours[Thursday] = 1
hours[Friday] = 0
Note the use of the Inc routine to increment the hours.

Counting down, using characters


We can also use single letters as the count type, because they are also ordinal types:

var
letter : Char;
begin
for letter := 'G' downto 'A' do
ShowMessage('Letter = '+letter)
end;
Letter = G
Letter = F
Letter = E
Letter = D
Letter = C
Letter = B
Letter = A
The For statements in the examples above have all executed one statement. If you want to execute more than one, you must
enclose these in a Begin and End pair.
Functions and procedures
An overview
A subroutine is like a sun-program. It not only helps divide your code up into sensible, manageable chunks, but it
also allows these chunks to be used (called) by different parts of your program. Each subroutine contains one of
more statements.

In common with other languages, Delphi provides 2 types of subroutine - Procedures and Functions. Functions
are the same as procedures except that they return a value in addition to executing statements. A Function, as its
name suggests, is like a little program that calculates something, returning the value to the caller. On the other
hand, a procedure is like a little routine that performs something, and then just finishes.

Parameters to subroutines
Both functions and procedures can be defined to operate without any data being passed. For example, you might
have a function that simply returns a random number (like the Delphi Random function). It needs no data to get it
going.

Likewise, you can have a procedure that carries out some task without the need for data to dictate its operations.
For example, you might have a procedure that draws a square on the screen. The same square every time it is
called.

Often, however, you will pass data, called parameters, to a subroutine. (Note that the definition of a subroutine
refers to parameters as arguments - they are parameters when passed to the subroutine).

Some simple function and procedure examples


The following code illustrates simple function and procedure definitions:

A procedure without parameters


procedure ShowTime; // A procedure with no parameters
begin
// Display the current date and time
ShowMessage('Date and time is '+DateTimeToStr(Now));
end;

// Let us call this procedure


ShowTime;
Date and time is 12/12/2002 15:30:45
Notice that we are using some Delphi run time library functions, marked in blue, in the above code. Click on
any to read more.

A procedure with parameters


procedure ShowTime(dateTime : TDateTime); // With parameters
begin
// Display the date and time passed to the routine
ShowMessage('Date and time is '+DateTimeToStr(dateTime));
end;

// Let us call this procedure


ShowTime(Yesterday);
Date and time is 11/12/2002
A function without parameters
function RandomChar : char;
var
i : integer;
begin
// Get a random number from 65 to 90
// (These numbers equate to characters 'A' to 'Z'
i := RandomRange(65, 90);

// Return this value as a char type in the return variable, Result


Result := Chr(i);
end;

// Let us call this function


ShowMessage('Char chosen is : '+RandomChar);
Char chosen is : A
It is important to note that we return the value from a function in a special variable called Result that Delphi
secretly defines for us to be the same type as the return type of the function. We can assign to it at any point
in the function. When the function ends, the value then held in Result is then returned to the caller.

A function with parameters


function Average(a, b, c : Extended) : Extended;
begin
// return the average of the 3 passed numbers
Exception handling in your code
Handling errors in Delphi
Whilst we all want to spend our time writing functional code, errors will and do occur in in code from time to time.
Sometimes, these are outside of our control, such as a low memory situation on your PC.

In serious code you should handle error situations so that at the very least, the user is informed about the error in your
chosen way.

Delphi uses the event handling approach to error handling. Errors are (mostly) treated as exceptions, which cause
program operation to suspend and jump to the nearest exception handler. If you don't have one, this will be the Delphi
default handler - it will report the error and terminate your program.

Often, you will want to handle the error, and continue with your program. For example, you may be trying to display a
picture on a page, but cannot find it. So you might display a placeholder instead. Much like Internet Explorer does.

Try, except where there are problems


Delphi provides a simply construct for wrapping code with exception handling. When an exception occurs in the wrapped
code (or anything it calls), the code will jump to the exception handling part of the wrapping code :

begin
Try
...
The code we want to execute
...
Except
...
This code gets executed if an exception occurs in the above block
...
end;
end;
We literally try to execute some code, which will run except when an error (exception) occurs. Then the except code
will take over.

Let us look at a simple example where we intentionally divide a number by zero :

var
number1, number0 : Integer;
begin
try
number0 := 0;
number1 := 1;
number1 := number1 div number0;
ShowMessage('1 / 0 = '+IntToStr(number1));
except
on E : Exception do
begin
ShowMessage('Exception class name = '+E.ClassName);
ShowMessage('Exception message = '+E.Message);
end;
end;
end;
When the division fails, the code jumps to the except block. The first ShowMessage statement therefore does not get
executed.

In our exception block, we can simpl place code to act regardless of the type of error. Or we can do different things
depending on the error. Here, we use the On function to act on the exception type.

The On clause checks against one of a number of Exception classes. The top dog is the Exception class, parent of all
exception classes. This is guaranteed to be activated above. We can pick out of this class the name of the actual
exception class name (EDivByZero) and the message (divide by zero).

We could have multiple On clauses for specific errors :

except
// IO error
On E : EInOutError do
ShowMessage('IO error : '+E.Message);
// Dibision by zero
On E : EDivByZero do
ShowMessage('Div by zero error : '+E.Message);
// Catch other errors
Else
ShowMessage('Unknown error');
end;

What happens when debugging


Dates and times
Why have a tutorial just on dates and times?
Because they are a surprisingly complex and rich subject matter. And very useful, especially since Delphi provides extensive
support for calculations, conversions and names.

The TDateTime data type


Date and time processing depends on the TDateTime variable. It is used to hold a date and time combination. It is also
used to hold just date or time values - the time and date value is ignored respectively. TDateTime is defined in the System
unit. Date constants and routines are defined in SysUtils and DateUtils units.

Let us look at some simple examples of assigning a value to a TDateTime variable:

var
date1, date2, date3 : TDateTime; // TDateTime variables
begin
date1 := Yesterday; // Set to the start of yesterday
date2 := Date; // Set to the start of the current day
date3 := Tomorrow; // Set to the start of tomorrow
date4 := Now; // Set to the current day and time
end;
date1 is set to something like 12/12/2002 00:00:00
date2 is set to something like 13/12/2002 00:00:00
date3 is set to something like 14/12/2002 00:00:00
date4 is set to something like 13/12/2002 08:15:45
Note : the start of the day is often called midnight in Delphi documentation, but this is misleading, since it would be
midnight of the wrong day.

Some named date values


Delphi provides some useful day and month names, saving you the tedium of defining them in your own code. Here they
are:

Short and long month names


Note that these month name arrays start with index = 1.

var
month : Integer;
begin
for month := 1 to 12 do // Display the short and long month names
begin
ShowMessage(ShortMonthNames[month]);
ShowMessage(LongMonthNames[month]);
end;
end;
The ShowMessage routine display the following information:

Jan
January
Feb
February
Mar
March
Apr
April
May
May
Jun
June
Jul
July
Aug
August
Sep
September
Oct
October
Nov
November
Dec
December
Short and long day names
It is important to note that these day arrays start with index 1 = Sunday. This is not a good standard (it is not ISO 8601
compliant), so be careful when using with ISO 8601 compliant routines such as DayOfTheWeek

var
Files
more:

FilePos Gives the file position in a binary or text file


Seek Moves to a new position in the file
SeekEof Skip to the end of the current line or file
SeekEoln Skip to the end of the current line or file

Getting information about files and directories


We have only covered data access to files. There are a number of routines that allow you to do all sorts of things with files
and directories that contain them:

ChDir Change the working drive plus path for a specified drive
CreateDir Create a directory
DeleteFile Delete a file specified by its file name
Erase Erase a file
FileExists Returns true if the given file exists
FileSearch Search for a file in one or more directories
FileSetDate Set the last modified date and time of a file
Flush Flushes buffered text file data to the file
GetCurrentDir Get the current directory (drive plus directory)
MkDir Make a directory
RemoveDir Remove a directory
Rename Rename a file
RenameFile Rename a file or directory
RmDir Remove a directory
SelectDirectory Display a dialog to allow user selection of a directory
SetCurrentDir Change the current directory
Truncate Truncates a file size

Using TStringList to read and write text files


The TStringList class is a very useful utility class that works on a lits of strings, each indexable like an array. The list can be
sorted, and supports name/value pair strings, allowing selection by name or value.

These lists can be furnished from text files in one fell swoop. Here we show a TStringList object being created, and loaded
from a file:

var
fileData : TStringList; // Our TStringList variable
begin
fileData := TStringList.Create; // Create the TSTringList object
fileData.LoadFromFile('Testing.txt'); // Load from Testing.txt file
...
We can display the whole file in a Memo box:

memoBox.Text := fileData.Text;
and we can display or process the file with direct access to any line at any time. In the example code below, we open a text
file, reverse all lines in the file, and then save it back. Not terribly useful, but it shows the power of TStringList.

var
fileData : TStringList;
saveLine : String;
lines, i : Integer;
begin
fileData := TStringList.Create; // Create the TSTringList object
fileData.LoadFromFile('Test.txt'); // Load from Testing.txt file

// Reverse the sequence of lines in the file


lines := fileData.Count;

for i := lines-1 downto (lines div 2) do


begin
saveLine := fileData[lines-i-1];
fileData[lines-i-1] := fileData[i];
fileData[i] := saveLine;
end;

// Now display the file


for i := 0 to lines-1 do
ShowMessage(fileData[i]);

fileData.SaveToFile('Test.txt'); // Save the reverse sequence file


end;
Take a look at TStringList to read more.
A dying art - using pointers
if msCount = maxCount then
begin
// First allocate a bigger memory space
GetMem(newMemoryStart, (maxCount + ALLOCATE_SIZE) * SizeOf(Int64));

// Copy the data from the old memory here


oldPtr := memStart;
newPtr := newMemoryStart;
for i := 1 to maxCount do
begin
// Copy one number at a time
newPtr^ := oldPtr^;
Inc(oldPtr);
Inc(newPtr);
end;

// Free the old memory


FreeMem(memStart);

// And now refer to the new memory


memStart := newMemoryStart;
nextSlot := memStart;
Inc(nextSlot, maxCount);
Inc(maxCount, ALLOCATE_SIZE);
end;

// Now we can safely add the number to the list


nextSlot^ := number;

// And update things to suit


Inc(msCount);
Inc(nextSlot);
end;

// Get the number at the index position (starting at 0)


function TNumberList.GetValue(index : Integer): Int64;
var
numberPtr : PInt64;
begin
// Simply get the value at the given Int64 index position
numberPtr := memStart;
Inc(numberPtr, index); // Point to the index'th Int64 number in storage
Result := numberPtr^; // And get the Int64 number it points to
end;

end.
And here is how the code could be used :

var
list : TNumberList;
value : Int64;
i : Integer;
begin
// Create a number list object
list := TNumberList.Create;

// Add the first 30 even numbers to the list, each doubled in size
for i := 0 to 29 do
list.Add(i * 2);

// Get the 22nd value = 44 (22 * 2)


value := list[22];
ShowMessage('22nd value = '+IntToStr(value));
end;
Printing text and graphics
// Write out the page size
Printer.Canvas.Font.Color := clRed;
Printer.Canvas.TextOut(40, 100, 'Page width = '+
IntToStr(Printer.PageWidth));
Printer.Canvas.TextOut(40, 180, 'Page height = '+
IntToStr(Printer.PageHeight));

// Increment the page number


Inc(page);

// Now start a new page - if not the last


if (page <= endPage) and (not Printer.Aborted)
then Printer.NewPage;
end;

// Finish printing
Printer.EndDoc;
end;
end;

end.
Object Orientation overview
What is object orientation?
Before the mid 1980's, the majority of programming languages used commercially operated in a procedural manner.
You could trace the code operation linearly line by line. The only jumps were as a result of conditional logic and
subroutine calls.

This was proving to be an error strewn method of writing large applications. You could happily modularise it by
packaging sub-programs into functions or procedures, but there was a limit to how these helped you. Not least
because subroutines do not hold onto their data across calls.

Also, with the advance of graphical interfaces, and a principally mouse click driven user interface, programs were
becoming event driven. They needed to respond to whatever of the many possible gui (graphical user interface)
objects on the screen the user chose to click next.

Object orientation radically changed the face of programming for many people. It took the concept of subroutines into
a completely different zone. Now they retained their data, and were in fact, collections of routines under one umbrella.
You called an object to do something, and left the object to sort out how it did it. You did not need to furnish the
internal data since it was retained across calls. For example, a list object could have items added or removed from the
list at will. And the object could be asked to sort the list into sequence.

Objects also allowed events, such as mouse clicks, to be handled neatly - a button object could call a routine (method)
of another object when clicked. This was now true moduralisation.

The basic parts of an object oriented program


The basic building block of an Object Oreinted (OO) program is a Class. It defines data (now called fields) and
functions and procedures (both now called methods). One class can have many fields and methods, just as we
described with our list object.

Additionally, OO introduced cleaner ways of allowing fields to be seen externally to the class - via Properties. Now a
class can have methods and properties - we seldom let fields be seen externally - they are used internally to make the
class work. A Property can be read only, write only or both. All very neat.

But a class is only a set of field, property and method definitions. It is a data type. We must create an instance of a
class before we can use it. We can make any number of instances of a single class. For example, we may make 3
different list class instances - one for CDs one for LPs and one for Cassettes. Each list object hides its storage of these
lists. We merely access its internal lists by the provided methods and properties. A read only property, for example,
may give the size of teh list as it stands at the moment.

Creating an instance of a class is called instantiation, and generates an object. Each instance of a class is a separate
object.

An example of a class and an object


Here we will define a very simple class that has one field, one property, and one method:

type
// Define a simple class
TSimple = class(TObject)
simpleCount : Byte;
property count : Byte
read simpleCount;
procedure SetCount(count : Byte);
end;
We have defined a class called TSimple as a new data type. It is a data type because we have to instantiate it to
create a variable. We create an object by calling the Create method. Ah ha! There is no Create method that can be
seen. This is because we have inherited it (see the Inherit tutorial for further) from the motherf of all classes :
TObject. This is the class we have based our class on. In fact, TObject is assumed by default, so we could have typed
:

TSimple = class
Our code has a field called simpleCount that is a Byte type. It can be read by the count property. When we do, we
use the property name count, rather than the internal simpleCount name.

Our method SetCount sets the simpleCount value. Before we go any further, we must define this method, or our code
will not compile:

// The TSimple class methods


procedure TSimple.SetCount(count : Byte);
begin
// Assign the passed count to the local variable
simpleCount := count;
end;
We simply store the passed SetCount parameter into the local variable. This means that someone who has created a
TSimple object can call SetCount to store a value internally. They can then use the count property to read it. This is
how:

var
simple : TSimple;
Avoiding memory leaks
Memory and Object Orientation
Object Orientation has transformed the process of application development. It has allowed complex code to be
written in nicely encapsulated modules (objects). When you create an object, Delphi handles the memory allocation
for the object as you call the Create method of the object class.

But there is a down side to this automation that is often overlooked, especially by newcomers, giving rise to
memory leaks.

What are memory leaks?


Put simply, every time you no longer use an object in your code, you should delete it, thereby freeing the
memory it was allocated. If you don't do this, your program can allocate more and more memory as it runs.
This failure to discard unwanted blocks of memory is called a memory leak. Run the program long enough and
you will use up the memory resources of your PC and the PC will slow down and eventually hang.

This is a very real problem in many programs, including commercial applications. If you are serious about your
code, you should follow the principles in this tutorial.

Why Delphi cannot free your memory for you


Delphi implements objects by reference. When you declare an object :

var
rover : TCar;
you are simply declaring a reference to an object. When you create an object for this reference :

begin
rover := TCar.Create;
Delphi allocates memory for the object, and executes the Create constructor method of the class to do any
object initialisation required. The rover variable now points to this new object. There is no magic link from the
object to the variable - simply a reference.

You can make a second reference to the object :

var
rover : Tcar;
myCar : TCar;

begin
rover := TCar.Create;
myCar := rover;
The myCar assignment simply makes the myCar variable point to the object that rover points to. No copying
of the object is done. Only one object exists at this time. And it is not magically linked to either variable.

Delphi does not keep track of who has referred to the object because it cannot easily keep track of all possible
variables that might refer to the object. So, for example, you could set both of these variables to nil and the
object will still persist.

How the memory leaks normally occur


In many parts of many programs, you will have a function or procedure that creates one or more objects in
order to carry out it's operation. Here is an example :

procedure PrintFile(const fileName : string);


var
myFile : TextFile;
fileData : TStringList;
i : Integer;

begin
// Create the TSTringList object
fileData := TStringList.Create; // This allocates object memory

// Load the file contents into the string list


fileData.LoadFromFile(fileName); // Expands the object memory size

// Open a printer file


AssignPrn(myFile);

// Now prepare to write to the printer


ReWrite(myFile);

// Write the lines of the file to the printer


for i := 0 to fileData.Count-1 do
WriteLn(myFile, fileData[i]);

// Close the print file


CloseFile(myFile);
end;
Inheritance
What is inheritance?
Inheritance in people is through their genes. These genes may give you your Mother's nose, or your Father's ear for
music, along with a lot else. You inherit features of your parents, and features of their parents, and indeed all human
beings. You are at the bottom of an enormous hierarchy of living things. But your inheritance is a framework - you add
your own features, and modify some of those inherited. You may be able draw when your parents cannot, but you are
not such a good cook maybe. Inheritance in Object oriented languages like Delphi has much the same features, as you
will see.

Inheritance in Delphi
Object oriented (OO) languages, as their name implies, revolve around another aspect of the real World, where a chunk
of code and data is treated as an object. An object in the real World, such as a football, has characteristics, such as size
(the data), and actions, such as being kicked (a method). Such objects in an OO language are defined in a class. A class
of objects, such as a class of balls, of which a football is a sub-class. The sub-class has specific features (such as a set
of sewn panels) but also inherits the parent class features. It too has a size, and can be kicked. In code terms, a
Football class would inherit the parent Ball class size variable (data), and kick method (code) :

type
// Define a Ball class
TBall = class
protected
ballSize : Byte;
ballSpeed : Byte;
published
procedure Kick(power : Byte);
function GetSpeed : Byte;
constructor Create(size : Byte);
end;

// Define a specialised ball


TFootball = class(TBall)
private
ballPanels : Byte;
published
// Different constructor - must pass panels now
constructor Create(size : Byte; panels : Byte);
end;
And here are the method implementations for these classes:

// Ball method implementations


procedure TBall.Kick(power : Byte);
begin
ballSpeed := (power * ballSize) DIV 4;
end;

function TBall.GetSpeed : Byte;


begin
Result := ballSpeed;
end;

constructor TBall.Create(size : Byte);


begin
ballSize := size;
ballSpeed := 0;
end;

// Football method implementations


constructor TFootball.Create(size : Byte; panels : Byte);
begin
Inherited Create(size); // Call the parent constructor first
ballPanels := panels; // Save the passed number of panels
end;
Ife we run the following code, creating and using object of these classes, we can see how the inheritance operates:

var
beachBall : TBall;
soccerBall : TFootball;
begin
// Create our two balls
beachBall := TBall.Create(5);
soccerBall := TFootball.Create(5, 12);

// How fast are they moving at the moment?


ShowMessageFmt('Beach ball is moving at speed : %d',[beachBall.GetSpeed]);
ShowMessageFmt('Soccer ball is moving at speed : %d',[soccerBall.GetSpeed]);
Abstraction
begin
// Save the passed parameter
sideLength := length;

// And set the side count to 4 - a square


sideCount := 4;
end;

// Main line code


procedure TForm1.FormCreate(Sender: TObject);
var
triangle : TTriangle;
square : TSquare;
begin
// Create triangle and square objects
triangle := TTriangle.Create(10);
square := TSquare.Create(10);

// Show the area of each polygon


ShowMessageFmt('Triangle with side length %d area = %f',
[triangle.length, triangle.GetArea]);

ShowMessageFmt('Square with side length %d area = %f',


[square.length, square.GetArea]);

// Now change the side length of the triangle and reshow


triangle.length := 5;
ShowMessageFmt('Triangle with side length %d area = %f',
[triangle.length, triangle.GetArea]);
end;
end.
The ShowMessage rountines display the following :

Triangle with side length 10 area = 50.0


Square with side length 10 area = 100.0
Triangle with side length 5 area = 12.5
Note that we have used triangle and square routines to get the area, and a polygon defined routine to get and
set the side length.

A tangible benefit of abstraction


If the above has appeared a little academic, then there is one nice benefit of this abstraction. Whilst an abstract
class such as a polygon cannot be used to create a new object (such an object would not be fully defined), you can
create a reference to one. By doing this, you can point this reference to any sub-class, such as a triangle, and use
the polygon class abstract methods. Your code can process arrays of sub-classes, and treat them all as if they were
generic polygons, without needing to know what variants they each are.

How do abstract classes differ from Interfaces?


Abstract classes and Interfaces are quite similar, in that they both provide placeholder methods. Their approach is
quite different though. Interfaces are not classes. Your class does not extend an interface. The nature of your class
has basically nothing to do with the interface. It can be a new class, or it can extend an existing class. The
placeholder methods in the Interface are implemented in your class. All of them must be implemented, in addition
to other methods in your class. By implementing the interface, your class simply adds a standard set of metods or
properties - a flavour in common with other classes implementing the interface.

Note also that a class may extend only one ancestor class, but can implement multiple interfaces.
Interfaces
begin
// Instantiate our bike and car objects
mumsBike := TBicycle.Create(false, 24);
dadsCar := TCar.Create('Nissan bluebird');

// Ask if each is recyclable


if dadsCar.isRecyclable
then ShowMessage('Dads car is recyclable')
else ShowMessage('Dads car is not recyclable');

if mumsBike.isRecyclable
then ShowMessage('Mums bike is recyclable')
else ShowMessage('Mums bike is not recyclable');
end;

end.
The ShowMessage shows the following program output:
Dads car is recyclable
Mums bike is not recyclable
Writing a class unit
// Check the first character of the fromStr
if stText[index] = fromStr[1] then
begin
if AnsiMidStr(stText, index, fromSize) = fromStr then
begin
// Increment the replace count
Inc(count);

// Store the toStr in the target string


newText := newText + toStr;

// Move the index past the from string we just matched


Inc(index, fromSize);

// Indicate that we have a match


matched := true;
end;
end;

// If no character match :
if not matched then
begin
// Store the current character in the target string, and
// then skip to the next source string character
newText := newText + stText[index];
Inc(index);
end;
end;

// Copy the newly built string back to stText - as long as we made changes
if count > 0 then stText := newText;

// Return the number of replacements made


Result := count;
end;
Notice that the Inc routine provided by Delphi has two methods of calling it. One with just the variable to be
incremented by 1. The second where we provide a different increment value. Here, we increment teh string
index by the length of the matching substring.

The whole of the Stringy is shown on the next page, along with sample code that illustrates use of it.

Page 1 of 2 | Using this Stringy unit


Using the new TStringy class
unit Main;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,
Stringy; // Use our new Stringy unit

type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);


var
myText : TStringy; // Define a TStringy variable
count : Integer; // Holds return value from TStringy method calls
position : Integer; // Gives the position during string searching
begin
// Create an instance of the TStringy class, with our desired text string
// Note that we have added TAB (#11),Carriage return (#13) and Line Feed (#10)
// characters to the string. These are recognised as word separators
myText := TStringy.Create('The cat sat'+#13#9+'on the'+#11+'BIG mat');

// Show the number of words in our string:


ShowMessage('Word count = '+IntToStr(myText.WordCount));

// Set up a new string to work on.


// This illustrates a 'write' property (WordCount can only be read)
myText.Text := 'In an enriched time there was a Rich man, with a rich sister';

// How many words in our new string?


ShowMessage('Word count now = '+IntToStr(myText.WordCount));

// Try to replace the word 'rich' with the word 'desolate'


count := myText.Replace('rich','desolate');

// How did the string replace get on?


ShowMessage('rich was replaced '+IntToStr(count)+' times');
ShowMessage(myText.Text);

// Now try to find the string 'rich' - it is no longer in the string


position := myText.FindFirst('rich');
if position > 0
then ShowMessage('''rich'' first index = '+IntToStr(position))
else ShowMessage('''rich'' was not found in the string now');

// We'll restore the string and look for all occurences of 'rich'
// Notice how the myText object remembers where it is in the following
// sequence of calls. This is a huge benefit of object orientation.
myText.Text := 'In an enriched time there was a Rich man, with a rich sister';
position := myText.FindFirst('rich');

while position > 0 do


begin
ShowMessage('''rich'' found at index : '+IntToStr(position));
// Find the next occurence
// Notice that myText also remembers the search string - we do not have
// to keep providing it.
position := myText.FindNext;
end;
end;

end.
Standard tab GUI components
GUI components
GUI stands for Graphical User Interface. It refers to the windows, buttons, dialogs, menus and everything visual in a
modern application. A GUI component is one of these graphical building blocks. Delphi lets you build powerful applications
using a rich variety of these components.

These components are grouped under a long set of tabs in the top part of the Delphi screen, starting with Standard at
the left. We'll look at this Standard tab here. It looks something like this (Delphi allows you to tinker with nearly
everything in its interface, so it may look different on your system):

Each of the components is itemised below with a picture of a typical GUI object they can create:

Menu PopupMenu GroupBox RadioGroup

Label

Edit

Button

CheckBox

RadioButton

ScrollBar

ListBox ComboBox Panel Memo

Frame : see text below ActionList : see text below

Note that the displayed components were taken from an XP computer. In order to get the new XP look (the XP 'themed'
GUI look), you must add the XP Manifest component to you form. It is found under the Win32 component tab:

XP Manifest component.

We'll now cover each of the components in turn. Components have many properties and methods and events, but we'll
keep the descriptions to the point to keep this article short enough. Each component is added to you form by clicking it
and then clicking (or dragging and releasing) on your form.

Frame objects
These were introduced in Delphi 5. They represent a powerful mechanism, albeit one that is a little advanced for a Delphi
Basics site. However, it is worth describing their role if you want to research further.

A frame is essentially a new object. It is defined using the File|New menu. Only then can you add the frame to your form
using the Frame component. You can add the same frame to as many forms of your application as you want. This is
because the frame is designed as a kind of template for a part of a form. It allows you to define the same look and feel
for that part of each form. And more importantly, each instance of the frame inherits everything from the original frame.

For further reading, Mastering Delphi by Cantu covers this topic with example code.

Menus

You might also like