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 Int2 : ShortInt; // -127 Int3 : Word; // 0 Int4 : SmallInt; // -32,768 Int5 : LongWord; // 0 Int6 : Cardinal; // 0 Int7 : LongInt; // -2,147,483,648 Int8 : Integer; // -2,147,483,648 Int9 : Int64; // -9,223,372,036,854,775,808 // Decimal data types : Dec1 : Single; // 7 Dec2 : Currency; // 50+ Dec3 : Double; // 15 Dec4 : Extended; // 19
significant significant significant significant
digits, digits, digits, digits,
to to to to to to to to to
255 127 65,535 32,767 4,294,967,295 4,294,967,295 2,147,483,647 2,147,483,647 9,223,372,036,854,775,807
exponent -38 to +38 fixed 4 decimal places exponent -308 to +308 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 Str2 Str3 Str4 Str5 Str6 Str7
: : : : : : :
Char; WideChar; AnsiChar; ShortString; String; AnsiString; WideString;
// // // // // // //
Holds Holds Holds Holds Holds Holds Holds
a single character, small alphabet a single character, International alphabet a single character, small alphabet a string of up to 255 Char's strings of Char's of any size desired strings of AnsiChar's any size desired 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
Byte ShortInt Word SmallInt LongWord Cardinal LongInt Integer Int64 Single Currency Double Extended
1 1 2 2 4 4* 4 4* 8
Range
0 -127 0 -32,768 0 0 -2,147,483,648 -2,147,483,648 -9,223,372,036,854,775,808
4 8 8 10
7 50+ 15 19
significant significant significant significant
digits, digits, digits, digits,
to to to to to to to to to
255 127 65,535 32,767 4,294,967,295 4,294,967,295 2,147,483,647 2,147,483,647 9,223,372,036,854,775,807
exponent -38 to +38 fixed 4 decimal places exponent -308 to +308 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; MANY = 300; RICH = 100000.00; var Age Books Salary Expenses TakeHome
: : : : :
begin Age Books Salary Expenses TakeHome TakeHome end; Age Books Salary Expenses TakeHome
Byte; SmallInt; Currency; Currency; Currency;
:= := := := := := is is is is is
YOUNG_AGE; MANY + 45; RICH; 12345.67; Salary; TakeHome -
set set set set set
to to to to to
// Small integer constant // Bigger integer constant // Decimal number : note no thousand commas // Smallest positive integer type // Bigger signed integer // Decimal used to hold financial amounts
// Assign // Assign // Assign // Assign // Assign Expenses;
from a predefined constant from a mix of constants (expression) from a predefined constant from a literal constant from another variable // Assign from an expression
23 345 100000.00 12345.67 87654.33
Numerical operators Number calculations, or expressions, have a number of primitive operators available:
+ * / div mod
: : : : : :
Add one number to another Subtract one number from another Multiply two numbers Divide one decimal number by another Divide one integer number by another 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; myDec : Single;
// Define integer and decimal variables
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'; if 'ABC' = 'abc' then ShowMessage('ABC if 'ABC' = 'ABC' then ShowMessage('ABC if 'ABC' < 'abc' then ShowMessage('ABC if 'ABC' <= 'abc' then ShowMessage('ABC if 'ABC' > 'abc' then ShowMessage('ABC if 'ABC' >= 'abc' then ShowMessage('ABC if 'ABC' <> 'abc' then ShowMessage('ABC end; ABC ABC ABC ABC
// String concatenation // Equality
= abc'); = ABC'); < abc'); <= abc'); > abc'); >= abc');
// Equality // Less than // Less than or equal // Greater than // Greater than or equal // Inequality
<> 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 := '12345678'; Insert('-+-', Target, 3);
// Target now = '3456' // 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); var suit : TSuit; begin suit := Clubs; end;
// Defines enumeration range // Defines enumeration variable // Set to one of the values
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 Day Day Day Day
1 2 3 4 5
= = = = =
Mon Tue Wed Thu 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 with John begin name := age := end;
our customer records do 'John Moffatt'; 67;
with Nancy do begin name := 'Nancy Moffatt'; age := 77; end; end;
// Only refer to the record fields
// Only refer to the record fields
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 Count Count Count Count
is is is is is
now now now now now
1 2 3 4 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] hours[Tuesday] hours[Wednesday] hours[Thursday] hours[Friday]
= = = = =
0 1 1 1 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 Letter Letter Letter Letter Letter Letter
= = = = = = =
G F E D C B 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 date2 date3 date4
is is is is
set set set set
to to to to
something something something something
like like like like
12/12/2002 13/12/2002 14/12/2002 13/12/2002
00:00:00 00:00:00 00:00:00 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 Seek SeekEof SeekEoln
Gives the file position in a binary or text file Moves to a new position in the file Skip to the end of the current line or file 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 CreateDir DeleteFile Erase FileExists FileSearch FileSetDate Flush GetCurrentDir MkDir RemoveDir Rename RenameFile RmDir SelectDirectory SetCurrentDir Truncate
Change the working drive plus path for a specified drive Create a directory Delete a file specified by its file name Erase a file Returns true if the given file exists Search for a file in one or more directories Set the last modified date and time of a file Flushes buffered text file data to the file Get the current directory (drive plus directory) Make a directory Remove a directory Rename a file Rename a file or directory Remove a directory Display a dialog to allow user selection of a directory Change the current directory 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; begin fileData := TStringList.Create; fileData.LoadFromFile('Testing.txt'); ...
// Our TStringList variable // Create the TSTringList object // 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; fileData.LoadFromFile('Test.txt');
// Create the TSTringList object // 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'); end; Take a look at TStringList to read more.
// Save the reverse sequence file
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
Frame : see text below
Panel
Memo
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