View Full Version : Delphi 6 Tutorial (Chapter 6)
_I_Play_Chess
10-19-2003, 09:37 AM
Chapter 6 page 0 of 14 or more:
-----------------------------------------
An introduction to Delphi Pascal
Before you start developing more sophisticated applications by using the RAD features of Delphi, you should learn the basics of the Delphi "PASCAL" language.
---------------------------------------
Welcome to the sixth chapter of the FREE online programming course:
A Beginner’s Guide to Delphi Programming.
Delphi Pascal: tutorials
Object (or Delphi) Pascal, a set of object-oriented extensions to standard Pascal, is the language of Delphi. Delphi Pascal is a high-level, compiled, strongly typed language that supports structured and object-oriented design. Its benefits include easy-to-read code, quick compilation, and the use of multiple unit files for modular programming.
Here's a list of tutorials, an introduction to Delphi Pascal, that will help you learn Delphi Pascal. Each tutorial will help you to understand a particular feature of Delphi Pascal language, with practical and easy to understand code snippets.
Variable Scope (See Page1)
-----------------------------------
Object Pascal Variable Scope: now you see me, now you don't.
Typed constants (See Page 2)
--------------------------------------
How to implement persistent values between function calls.
Loops (See Page 3)
-------------------------
Repeating operations in Object Pascal in Object Pascal in Object Pascal in Object Pascal.
Decisions (See Page 4)
-----------------------------
Making decisions in Object Pascal or NOT.
Functions and Procedures (See Page 5)
--------------------------------------------------
Creating user defined subroutines in Object Pascal.
Routines in Delphi: Beyond the Basics (See Page 6)
-----------------------------------------------------------------
Extending Object Pascal functions and procedures with default parameters and method overloading.
Statements/Properties/Variables (See Page 7)
----------------------------------------------------------
The basic layout of a Pascal/Delphi program.
String Types in Delphi (See Page 8)
---------------------------------------------
Understanding and managing string data types in Delphi's Object Pascal. Learn about differences between Short, Long, Wide and null-terminated strings.
Ordinal and Enumerated Data Types (See Page 9)
---------------------------------------------------------------
Extend Delphi's built-in types by constructing your own types.
Arrays in Object Pascal (See Page 10)
------------------------------------------------
Understanding and using array data types in Delphi.
Records in Delphi (See Page 11)
----------------------------------------
Learn about records, Delphi's Pascal data structure that can mix any of Delphi's built in types including any types you have created.
Variant Records in Delphi (See Page 12)
--------------------------------------------------
Why and when to use variant records, plus creating an array of records.
Pointers in Delphi (See Page 13)
-----------------------------------------
An introduction to pointer data type in Delphi. What are pointers, why, when and how to use them.
Recursions in Delphi (See Page 14)
--------------------------------------------
Writing and using recursive functions in Object Pascal.
Some exercises for you...
Since this Course is an online course, there is much you can do to prepare for the next chapter. At the end of each chapter I'll try to provide several tasks for you to get more familiar with Delphi and the topics we discuss in the current chapter. Here are some suggested actions for you, after you finish reading this chapter:
• Be sure to visit Delphi Code Puzzles (See Page 15)
.................................................. .................................
- the place where you compete against yourself and the Delphi compiler for fame and glory. Your task is to solve Delphi Code Puzzle by assembling a program code one line at a time. For each code line, your will be presented with four options. Each option represents a line of code. Only one line will be the correct pick - sometimes even more of them. You'll know if you've picked the correct line only when you finish the puzzle.
To the next chapter: A Beginner's Guide to Delphi Programming
This is the end of the sixth chapter, in the next chapter, we'll deal with more sophisticated articles on Delphi Pascal.
_I_Play_Chess
10-19-2003, 09:45 AM
VARIABLE SCOPE
---------------------
Delphi For Beginners:
Object Pascal Variable Scope
------------------------------------
As mentioned in some of the previous articles understanding Object Pascal variable scope is one of key elements in building applications with Delphi/Object Pascal.
Scope of Variables and Constants
The term scope refers to the availability of a variable or constant declared (or used) in one part of a program to other parts of a program.
Unless we specify otherwise, changing the value of a variable named, let's say, SomeNumber in one procedure (function) will not affect another variable with the same name in another procedure (function).
Since Delphi requires us to declare variables, it's a lot harder to fall into the trap caused by side effects accidentally. As we know by now, every variable used in some procedure has to be declared in the var section of the event handler.
In general, we declare a variable where we want to use it. For example, if we want to use a variable in an event handler, we declare the variable within the event handler.
Local Scope (+ variable declaration and initialization)
Most variables have local scope, which means that the variable is visible only within the code block in which it is declared (usually: Event Handler for some method). In particular, an event handler will not normally have access to the value of a variable in another event handler.
If we want to be sure a variable is local within an event handler, we have to declare it in the var section inside the event handler. Since we must declare a variable before we can use it, if we can use a variable without declaring it locally, we know that there is a variable with greater scope with the same name somewhere around project.
Let us look at the first example:
1. Start Delphi, this will give us (by default) new application with one blank form.
2. Double click somewhere on the form (to create OnCreate event handler)
3. Write down this code:
-------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(FloatToStr(SomeNumber));
end;
-------------------------------
4. If you try to run your project now, you will be prompted with: "Undeclared Identifier: 'SomeNumber'" error. This means that we haven't declared SomeNumber variable in our project (note: entire project, not FormCreate event handler).
5. To declare SomeNumber variable as double type change your code to:
------------------------------
procedure TForm1.FormCreate(Sender: TObject);
var SomeNumber: double;
begin
ShowMessage(FloatToStr(SomeNumber));
end;
------------------------------
6. Run your project, message box will appear with some strange (value of the memory region where variable is stored) number. Delphi will also give us "Variable 'SomeNumber' might not have been initialized" warning. This means that, before using declared variable, it is a good practice to initialize it (just to be sure). For that purpose add this line of code before ShowMessage...
SomeNumber := 123.45;
7. Now, when you run your project message box will display 123,45 (no errors, no warnings).
Finally, we can see why local variables are called local...
8.Add one TButton component to form and double-click it to create Buttons OnClick event handler. Add the following code (so that OnClick looks like):
-----------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(FloatToStr(SomeNumber));
end;
-----------------------------
Again we have "Undeclared Identifier: 'SomeNumber'" error. This is what local variables are all about: even if we have declared (and initialized) SomeNumber variable in OnCreate event handler of the form, SomeNumber is not accessible in the OnClick handler of TButtton. We simply cannot use SomeNumber (with 123.45 value) in the OnClick (at least for now). This means that SomeNumber from OnCreate and SomeNumber from OnClick are two different variables (that can, of course, hold different values)
9. Don't close this project, jet. We will need it again...
10. Add the following line before ShowMessage in the OnClick event handler (we will need it later, don't worry about this for now)
SomeNumber:=555.55;
Sharing variables across procedures (event handlers)
Occasionally we will want to share the values of variables (and constants) across event handlers or across units. For example, if an application is designed to perform a calculation involving one SomeNumber at a time, that SomeNumber should be available to all procedures in a unit.
Depending on where we declare a variable, the variable can be thought of as a true global variable accessible by any other code in the application, or a unit-level variable accessible by any code in the unit.
Unit level variables - unit level scope
We put the declaration statements for unit-level variables in a var section in the unit's implementation section. Unit-level constants are declared in a const section.
Let's look at the second example:
0. We will be modifying our first example (be sure to have it)
1. Add declaration of SomeNumber, so that implementation code of the unit looks like:
--------------------------
...
implementation
**$R *.DFM}
var
SomeNumber: Double;
...
--------------------------
2. Run your program. As you can see, we don't have "Undeclared Identifier: 'SomeNumber'" error in OnClick handler of the TButton. When program starts message box will appear with 123.45 value. When you click on the Button1 message box will display 555.55; that's why we need step 10 in the first example - we have initialized SomeNumber to 555.55 in the OnClick event of the Button1.
What's this? We have two SomeNumber variables in our project and they both hold different values.
Obviously, we have to be careful when assigning values to unit-level variables. Although we can use the same variable (or constant) name for both local and unit-level variables, this is not a good idea. Any var (or const) declaration contained in a procedure takes precedence over global (unit-level) declarations. Duplicating the names makes the global variable invisible to the procedure (Delphi doesn't tell us whether a global variable has been defined with the same name as a local variable). That is why SomeNumber holds the 123,45 value in the OnCreate event handler of the form (we cannot use global variable SomeNumber in the OnCreate procedure)
Note 1: If you really have to use two variables with the same SomeNumber name (one global and one local), you can access the global SomeNumber variable value in the forms OnCreate procedure with the call to unit1. SomeNumber (unit1 is the name of the unit with the global SomeNumber variable). That is, something like
unit1.SomeNumber:=444.44;
will change value of the global variable SomeNumber inside OnCreate event handler of the form (remember that there is a SomeNumber variable local to this procedure which will stay unchanged)
Note 2: As global SomeNumber is global to the unit we can access (more important: change) its value from any other procedure inside this unit. However click to Button 1 will reset SomeNumber value to 555.55. Better way to initialize global variables is inside initialization section of the unit.
Global variables - program level scope
If we want to create true global variables (or/and constants) in a project, we have to place the declaration in the interface section of the unit. Variables declared in the "interface" section will be visible (accessible) to any unit which "uses" that unit.
For example, to change SomeNumber variable value that is declared in Unit1 from Unit2, use this statement:
Unit1.SomeNumber:=999.99;
Be sure to add Unit1 to the uses clause of Unit2.
Conclusion
That's it. I hope you have had the power to come to the end of this article. As we can see, there is much to be stated about variable scope in Object Pascal. Of course, there is more: static variables (or typed constants) are something we could name "constant variables".
_I_Play_Chess
10-19-2003, 09:51 AM
TYPED CONSTANTS
-----------------------
Delphi For Beginners:
How to implement persistent values between function calls.
When Delphi invokes an event handler, the old values of local variables are wiped out. What if we want to keep track of how many times a button has been clicked? We could have the values persist by using a unit-level variable, but it is generally a good idea to reserve unit-level variables only for sharing information. What we need are usually called static variables or typed constants in Delphi.
Variable or constant?
Typed constants can be compared to initialized variables-variables whose values are defined on entry to their block (usually event handler). Such a variable is initialized only when the program starts running. After that, the value of a typed constant persists between successive calls to their procedures.
Using typed constants is a very clean way of implementing automatically initialized variables. To implement these variables without typed constants, we'll need to create an initialization section that sets the value of each initialized variable.
Variable typed constants
Although we declare typed constants in the const section of a procedure, it is important to remember that they are not constants. At any point in your application, if you have access to the identifier for a typed constant you'll be able to modify its value.
To see typed constants at work, put a button on a blank form, and assign the following code to the OnClick event handler:
-----------------------------
procedure TForm1.Button1Click(Sender: TObject);
const clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks);
clicks := clicks + 1;
end;
-----------------------------
Notice that every time you click on the button, forms caption increments steadily.
Now try the following code:
-----------------------------
procedure TForm1.Button1Click(Sender: TObject);
var clicks : Integer;
begin
Form1.Caption := IntToStr(clicks);
clicks := clicks + 1;
end;
-----------------------------
We are now using uninitialized variable for the clicks counter. Notice that weird value in the forms caption after you click on the button.
Constant typed constants
You have to agree that idea of modifiable constants sound a bit strange. In 32 bit versions of Delphi (2,3,4), Borland decided to discourage their use, but support them for Delphi 1 legacy code.
We can enable or disable Assignable typed constants on the Compiler page of the Project Options dialog box.
If you've disabled Assignable typed constants for a given project, when you attempt to compile previous code Delphi will give you 'Left side cannot be assigned to' error upon compilation. You can, however, create assignable typed constant by declaring:
------------------------------
**$J+}
const clicks : Integer = 1;
**$J-}
------------------------------
Therefore, the first example code looks like:
------------------------------
procedure TForm1.Button1Click(Sender: TObject);
**$J+}
const clicks : Integer = 1; //not a true constant
**$J-}
begin
Form1.Caption := IntToStr(clicks);
clicks := clicks + 1;
end;
------------------------------
Conclusion
It's up to you to decide whether you want typed constants to be assignable or not. Important thing here is that besides ideal for counters, typed constants are ideal for making components alternately visible or invisible, or we can use them for switching between any Boolean properties. Typed constants can also be used inside TTimer's event handler to keep track of how many times even has been triggered.
_I_Play_Chess
10-19-2003, 10:13 AM
LOOPS
---------
Delphi For Beginners:
Repeating operations in Object Pascal.
while language = Delphi do
begin
Use(language);
end;
Loops
The loop is a common element in all programming languages. Object Pascal has three control structures that execute blocks of code repeatedly: for, repeat-until and while-do.
The FOR loop
Suppose we need to repeat an operation a fixed number of times.
---------------------
// show 1,2,3,4,5 message boxes
var i: integer;
begin
for i := 1 to 5 do
begin
ShowMessage('Box: '+IntToStr(i));
end;
end;
----------------------
The value of a control variable (i), which is really just a counter, determines how many times a for statement runs. The keyword for sets up a counter. In the preceding example, the starting value for the counter is set to 1. The ending value is set to 5.
When the for statement begins running the counter variable is set to the starting value. Delphi than checks whether the value for the counter is less than the ending value. If the value is greater, nothing is done (program execution jumps to the line of code immediately following the for loop code block). If the starting value is less than the ending value, the body of the loop is executed (here: the message box is displayed). Finally, Delphi adds 1 to the counter and starts the process again.
Sometimes it is necessary to count backward. The downto keyword specifies that the value of a counter should be decremented by one each time the loop executes (it is not possible to specify an increment / decrement different than one). An example of a for loop that counts backward.
-------------------------
var i: integer;
begin
for i := 5 downto 1 do
begin
ShowMessage('T minus ' + IntToStr(i) + 'seconds');
end;
ShowMessage('For sequence executed!');
end;
--------------------------
It's important that you never change the value of the control variable in the middle of the loop. Doing so will cause errors.
Nested FOR loops
Writing a for loop within another for loop (nesting loops) is very useful when you want to fill / display data in a table or a grid.
-------------------------
var i,j: integer;
begin
//be aware:
//this double loop is executed 4x4=16 times
for i:= 1 to 4 do
for j:= 4 downto 1 do
ShowMessage('Box: '+
IntToStr(i)+ ',' +
IntToStr(j));
end;
--------------------------
The rule for nesting for-next loops is simple: the inner loop (j counter) must be completed before the next statement for the outer loop is encountered (i counter). We can have triply or quadruply nested loops, or even more.
Note: Generally, the begin and end keywords are not strictly required, as you can see. If begin and end are not used, the statement immediately following the for statement is considered the body of the loop.
The WHILE and REPEAT loops
Sometimes we won't know exactly how many times a loop should cycle. What if we want to repeat an operation until we reach a specific goal?
The most important difference between the while-do loop and the repeat-until loop is that the code of the repeat statement is always executed at least once.
The general pattern when we write a repeat (and while) type of loop in Delphi is as follows:
-----------------------------
repeat
begin
statements;
end;
until condition = true
------------------------------
while condition = true do
begin
statements;
end;
------------------------------
Here goes the code to show 5 successive message boxes using repeat-until.
------------------------------
var i: integer;
begin
i:=0;
repeat
begin
i:=i+1;
ShowMessage('Box:'+IntToStr(i));
end;
until i>5;
end;
------------------------------
As we can see, the repeat statement evaluates a condition at the end of the loop (therefore repeat loop is executed for sure at least once).
The while statement, on the other hand, evaluates a condition at the beginning of the loop. Since the test is being done at the top, we will usually need to make sure that the condition makes sense before the loop is processed, if this is not true the compiler may decide to remove the loop from the code.
-----------------------------
var i: integer;
begin
i:=0;
while i<5 do
begin
i:=i+1;
ShowMessage('Box:'+IntToStr(i));
end;
end;
------------------------------
Break and Continue
The Break and Continue procedures can be used to control the flow of repetitive statements: The Break procedure causes the flow of control to exit a for, while, or repeat statement and continue at the next statement following the loop statement. Continue allows the flow of control to proceed to the next iteration of repeating operation. See how they work.
Using Break and Continue in loops.
Break.
Use break to terminate, break out of an enclosing for, while or repeat looping early.
---------------------------------
var
I:Integer;
Index:Integer;
Ints:array[1..100]of Integer;
begin
for Index:=Low(Ints) to High(Ints) do
begin
if Ints[Index]=I then
begin
ShowMessage('Index of '+IntToStr(I)+'='+IntToStr(Index));
Break;
end;
end;
end;
-----------------------------------
The above example shows a loop scaning sequencally a array of integers for the first number I. Once its found I it then displays here it is in the array. Now we have found I there is no need for any more iterations so we can break out of loop thus saving much processing.
Note that break will respect any try finally statments. If break is called from within a try part of a try finally enclosed by loop the finally will be entered.
Continue.
Use Continue to force an enclosing for, while or repeat loop into its next iteration skipping the remains of the current iteration.
Notice that://or{ In Delphi are used for NOTES.
-----------------------------------
for Index:=0 to ListBox1.Items.Count-1 do
begin
{Check file name selected in list box.}
if not(ListBox1.Selected[Index]) then Continue;
{Check file exists.}
if not(FileExists(ListBox1.Items[Index])) then Continue;
{Process file...}
end;
-----------------------------------
The above example shows a loop scaning a list box which contains a list of file names. For each file in the list it firsts checks if it is selected and then that the file exists. If a check fails it just continues to the next file in the list.
Note that continue will respect any try finally statments. If continue is called from within a try part of a try finally enclosed by loop the finally will be entered.}.
_I_Play_Chess
10-19-2003, 10:46 AM
DECISIONS
--------------
Delphi For Beginners:
Making decisions in Object Pascal.
if language= Delphi then
begin
Use(language)
end
else Skip(language);
Branching
If we want to control the flow of code execution depending on what the program has already done or what it has just encountered well need to use one of the two Object Pascal's branching statements: if statements and case statements.
if then else statement
The if statement is used to test for a condition and then execute sections of code based on whether that condition is True or False. The condition is described with a Boolean expression, If the condition is True, the code flow branches one way. If the condition is False, the flow branches in another direction.
Let's see this behavior on an example:
------------------------
var iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
if iNumber = 0 then
ShowMessage('Zero value encountered!');
end;
-------------------------
If the number (assigned to iNumber variable) is 0, the expression iNumber = 0 evaluates to True and the message is displayed; otherwise, nothing is displayed. If we want more than one thing to happen when the tested condition is True, we can write multiple statements in a begin..end block.
-------------------------
var iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
if iNumber = 0 then
begin
ShowMessage('Zero value encountered!');
Exit; // exit from the current procedure
end;
//if iNumber is 0 the folowing
//code will never be executed
ShowMessage('Nobody likes 0, ha!');
end;
--------------------------
More often, we will want to process multiple statements if a condition is True or False.
--------------------------
var iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
if iNumber < 0 then
begin
//statements ...
ShowMessage('Your number is negative!');
//statements ...
end
else
begin
//statements ...
ShowMessage('Your number is positive or zero!');
//statements ...
end;
end;
----------------------------
Note: Each statement in the begin..end block ends with a semicolon. We cannot have a semicolon before or after the else keyword. The if-then-else statement, is a single statement, therefore we cannot place a semicolon in the middle of it.
An if statement can be quite complex. The condition can be turned into a series of conditions (using the and, or and not Boolean operators), or the if statement can nest a second if statement.
-----------------------
var iNumber : Integer;
begin
if iNumber = 0 then
begin
ShowMessage('Zero number not allowed!');
exit;
end
else
//no need to use begin-end here
if iNumber < 0 then
ShowMessage('Your number is negative!')
else
ShowMessage('Your number is positive!');
end;
------------------------
Note: When you write nested if statements choose a consistent, clear indentation style. This will help you and anyone else who reads your code see the logic of the if statement and how the code flows when your application runs.
The case statement
Although, we can use the if statement for very complex (nested) condition testing, the case statement is usually easier to read (debug!) and the code runs more quickly.
The case statement makes it clear that a program has reached a point with many branches; multiple if-then statements do not.
------------------------
var iNumber : Integer;
begin
//some value must be
//assigned to iNumber here!
begin
case iNumber of
0:
ShowMessage('Zero value');
1..10 :
ShowMessage('Less than 11, greater than 0');
-1, -2, -3 :
ShowMessage('Number is -1 or -2 or -3');
else
ShowMessage('I do not care');
end;
end;
--------------------------
What follows the select keyword is usually called the selector. The selector is a variable or expression taken from either the char type or any integer type (an ordinal type.)
As you can see, the individual case statements use a single constant, a group of constants (separated by comma), or a range of constants (double dot separated). We can even add an else keyword to take care of all the remaining cases at once.
Note: Only one case statement will be executed, we cannot have overlapping conditions in the case statements.
Note: If you want to include more than one statement in the part following the colon (:), place the begin and end keywords around the multiple statements.
_I_Play_Chess
10-19-2003, 10:51 AM
Note: If you want to include more than one statement in the part following the colon ":" instead of (:)Smilies enabled in the text)
place the begin and end keywords around the multiple statements.
_I_Play_Chess
10-19-2003, 10:57 AM
FUNCTIONS AND PROCEDURES
---------------------------------------
Delphi For Beginners:
Creating user defined subroutines.
Have you ever found yourself writing the same code over and over to perform some common task within event handlers? Yes! It's time for you to learn about programs within a program. Let's call those mini programs subroutines.
Introduction to subroutines
Subroutines are an important part of any programming language, and Object Pascal is no exception. In Delphi, there are generally two types of subroutines: a function and a procedure. The usual difference between a function and a procedure is that a function can return a value, and a procedure generally will not do so. A function is normally called as a part of an expression.
Take a look at the following examples:
-------------------------
procedure SayHello(const sWhat:string);
begin
ShowMessage('Hello ' + sWhat);
end;
function YearsOld(const BirthYear:integer): integer;
var Year, Month, Day : Word;
begin
DecodeDate(Date, Year, Month, Day);
Result := Year - BirthYear;
end;
-------------------------
Once subroutines have been defined, we can call them one or more times.
-------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
SayHello('Delphi User');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
SayHello('TGuide');
ShowMessage('You are ' +
IntToStr(YearsOld(1973)) +
' years old!');
end;
--------------------------
Functions and Procedures
As we can see, both functions and procedures act like mini programs. In particular, they can have their own type, constants and variable declarations inside them.
Take a closer look at a (miscellaneous) SomeCalc function:
--------------------------
function SomeCalc
(const sStr: string;
const iYear, iMonth: integer;
var iDay:integer): boolean;
begin
...
end;
--------------------------
Every procedure or function begins with a header that identifies the procedure or function and lists the parameters the routine uses, if any. The parameters are listed within parentheses. Each parameter has an identifying name and usually has a type. A semicolon separates parameters in a parameter list from one another.
sStr, iYear and iMonth are called constant parameters. Constant parameters cannot be changed by the function (or procedure). The iDay is passed as a var parameter, and we can make changes to it, inside the subroutine.
Functions, since they return values, must have a return type declared at the end of the header. The return value of a function is given by the (final) assignment to its name. Since every function implicitly has a local variable Result of the same type as the functions return value, assigning to Result has the same effect as assigning to the name of the function.
Positioning and calling subroutines
Subroutines are always placed inside the implementation section of the unit. Such subroutines can be called (used) by any event handler or subroutine in the same unit that is defined after it. (I will write about recursions in some of future articles.)
Be aware: the uses clause of a unit tells you which units it can call. If we want a specific subroutine in a Unit1 to be usable by the event handlers or subroutines in another unit (say Unit2), we have to:
add a Unit1 to the uses clause of Unit2
place a copy of the header of the subroutine in the interface section of the Unit1.
This means that subroutines whose headers are given in the interface section are global in scope.
When we call a function (or a procedure) inside its own unit, we use its name with whatever parameters are needed. On other hand, if we call a global subroutine (defined in some other unit, e.g. MyUnit) we use the name of the unit followed by a period.
-----------------------
...
//SayHello procedure is defined inside this unit
SayHello('Delphi User');
//YearsOld function is defined inside MyUnit unit
Dummy := MyUnit.YearsOld(1973);
...
-----------------------
Note: Functions or procedures can have their own subroutines embedded inside them. An embedded subroutine is local to the container subroutine and cannot be used by other parts of the program. Something like:
------------------------
procedure TForm1.Button1Click(Sender: TObject);
function IsSmall(const sStr:string):boolean;
begin
//IsSmall returns True if sStr is
in lowercase, False otherwise
Result:=LowerCase(sStr)=sStr;
end;
begin
//IsSmall can only be uses
inside Button1 OnClick event
if IsSmall(Edit1.Text) then
ShowMessage('All small caps in Edit1.Text')
else
ShowMessage('Not all small caps in Edit1.Text');
end;
-------------------------
Passing arrays
Delphi allows us to use open parameters for passing arrays to subprograms. This gives us the flexibility of passing array to a subroutine without needing to declare the array's limits. Open-array parameters can be constant and variable parameters.
The MakeZero procedure in the next example assigns zero to each element of an array of Double, Because the Arr parameter is an open-array parameter, the subroutine can operate on any array with an element type of Double.
------------------------
procedure MakeZero(var Arr: Array of Double)
var i:integer;
begin
for i:= Low(Arr) to High(Arr) + 1 do Arr[i] := 0;
end;
------------------------
_I_Play_Chess
10-19-2003, 11:03 AM
ROUTINES IN DELPHI: BEYOND THE BASICS
-------------------------------------------------------
Delphi For Beginners:
Extending Object Pascal's functions and procedures with default (optional) parameters and method overloading.
As we know, functions and procedures are an important part of the Object Pascal language. Starting with Delphi 4, Object Pascal enables us to work with functions and procedures that support default parameters (making the parameters optional), and permits two or more routines to have identical name, but operate as totally different routines.
Let's see how Overloading and default parameters can help us code better...
Overloading
Simply put overloading is declaring more than one routine with the same name. Overloading allows us to have multiple routines that share the same name, but with different number of parameters and their types.
As an example, let's consider the following two functions:
--------------------------
{Overloaded routines must be declared
with the overload directive}
function SumAsStr(a, b :integer): string; overload;
begin
Result := IntToStr(a + b);
end;
function SumAsStr
(a, b : extended; Digits:integer): string; overload;
begin
Result := FloatToStrF(a + b, ffFixed, 18, Digits);
end;
--------------------------
These declarations create two functions, both called SumAsStr, that take different number of parameters, that are of different types. When we call an overloaded routine, the compiler must be able to tell which routine we want to call.
For example, SumAsStr(6, 3) calls the first SumAsStr function, because its arguments are integer-valued. On the other hand, if we try to call the SumAsStr function as follows:
SomeString := SumAsStr(6.0,3.0)
we'll get the following error: "there is no overloaded version of 'SumAsStr' that can be called with these arguments", meaning that we should also include the Digits parameter used to specify the number of digits after the decimal point.
Note: there is only one rule when writing overloaded routines: an overloaded routine must differ in at least one parameter type. The return type, instead, cannot be used to distinguish among two routines.
Two units - one routine
Let's say we have one routine in unit A, and unit B uses unit A, but declares a routine with the same name. The declaration in unit B does not need the overload directive: we should use unit A's name to qualify calls to A's version of the routine from unit B. Something like:
-----------------------
unit B;
...
uses A;
...
procedure RoutineName;
begin
Result := A.RoutineName;
end;
-----------------------
An alternative to using overloaded routines is to use default parameters, which usually results in less code to write and maintain.
Default parameters
In order to simplify some statements we can give a default value for the parameter of a function or procedure, and we can call the routine with or without the parameter, making it optional.
To provide a default value, end the parameter declaration with the = symbol followed by a constant expression.
For example, given the declaration
----------------------
function SumAsStr
(a,b : extended; Digits : integer = 2): string;
----------------------
the following function calls are equivalent.
SumAsStr(6.0, 3.0)
SumAsStr(6.0, 3.0, 2)
-----------------------
Note: parameters with default values must occur at the end of the parameter list, and must be passed by value or as const. A reference (var) parameter cannot have a default value.
When calling routines with more than one default parameter we cannot skip parameters (like in VB):
----------------------------
function SkipDefParams
(var A:string; B:integer=5, C:boolean=False):boolean;
...
//this call generates an error message
CantBe := SkipDefParams('delphi', , True);
----------------------------
Overloading with default parameters
When using both function or procedure overloading and default parameters, don't introduce ambiguous routine declarations.
Consider the following declarations:
----------------------------
procedure DoIt(A:extended; B:integer = 0); overload;
procedure DoIt(A:extended); overload;
----------------------------
The call to DoIt procedure like DoIt(5.0) does not compile. Because of the default parameter in the first procedure, this statement might call both procedures, because it is impossible to tell which procedure is meant to be called.
_I_Play_Chess
10-19-2003, 11:17 AM
STATEMENTS PROPERTIES VARIABLES
-----------------------------------------------
Delphi for Beginners:
The basic layout of a Pascal/Delphi program.
Starting with this article I'll try to present fundamentals of Object Pascal Programming.
If you are new to Delphi, consider reading previous features for Delphi Beginners:
Why Delphi? (what is Delphi and what can it do for you)
Getting Started (an overview of Delphi programming and a simple application)
Working With Components (introduction to Delphi VCL)
Database Form Wizard (developing my own, first Delphi database application)
Visual programming is fun. Unfortunately, event handlers that you've seen, in those articles, didn't do much. To fully understand and take advantage of Delphi, we must become comfortable with the dialect of Object Pascal built into Delphi.
Statements or Long live the ";"
A statement in Delphi should be thought as a complete "sentence." Generally, statements describe algorithmic actions that can be executed. Statements do things to Delphi objects. These objects may be properties of components, numeric expressions or some other more exotic gadgets.
Delphi distinguishes between simple statement and compound statements. A simple statement is one executable line of code (not one line of code in Code Editor). A compound statement is a block of simple statements surrounded by begin/end keywords.
//simple statement
Form1.Caption := 'Ha! New caption ';
//compound statement
begin
Top := Top - 10;
Width := Width + 5;
end
As you can see, a semicolon (;) is always required to separate two simple statement.
Comments or Notes:
Comments are lines of text in your code that are there for documentation purposes. Comments do not take up any room in the compiled code. There are three ways to indicate a comment:
// one-line comment
{this is also a comment - can be several lines}
(* this is another comment *)
Commenting out executable statements to help debug your programs is a common and often necessary technique in programming languages. You'd be surprised how quickly you forget what code you wrote is supposed to do.
Properties and Values or Long Live the ":="
To design the look of your application interface, you set the values of object properties using the Object Inspector. This is referred to as making design time settings. On the other hand, resetting properties via code (run time settings) is one of the most common tasks in Delphi. Any property you can set at design time can also be set at runtime by using code. There are also properties that can be accessed only at runtime. These are known as runtime-only properties.
When we want to change a property setting (value) for a Delphi object we have to place the object's name followed by a period (.) and the name of the property on the left side of the colon equal (:=) combination - this is not some kind of a smiley.
ObjectName.Property := Value;
When you need to reset a large number of properties at one time, you probably would not prefer to have to retype the name of the object each time. The with keyword lets you do this by eliminating the need to type the object name over and over again.
Enough theory. Let's take a look at a real example:
---------------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
Form1.Caption := 'Ha! New caption ';
Button1.Left := Button1.Left - 10;
with Button1 do begin
Top := Top - 10;
Width := Width + 5;
end
end;
-----------------------------------
(See picture "illustration 1" at the end of this chapter:
Variables
Variables in Pascal hold informations (values). Variables have to be declared before they can be used. We do this after the var keyword. The var keyword can be used in several places in the code, such as at the beginning of the code of a function or procedure, to declare variables local to the routine, or inside a unit to declare global variables.
When declaring a variable, we must state its type. The type of a variable defines the set of values the variable can have.
------------------------------
var
SomeValue : Integer;
NewAdress : string;
IsThisWorthy : boolean;
GimmeMoney : Currency;
Something : variant;
A, B, C : char;
Lookout : Pointer;
-------------------------------
As you can see, we can declare more than one variable of the same type by placing a comma between them. (SomeValue is variable of Integer type.)
Assigning values to variables
After you declare a variable, you can then use it to manipulate data in memory. Just as for setting properties, Delphi uses the := sign for assigning values to variables. Like:
--------------------------------
GimmeMoney := 323,23; //Curreny type variable
IsThisWorthy := True; //boolean type
NewAdress := 'My new home adress'; //string type
--------------------------------
Delphi enforces strict rules on what kind of assignments we can make between variable types. This means that something like
GimmeMoney := 'one thousand';
will give "Incompatible Types" error message. We can't assign string value to currency type variable.
Another example (you should really try this one, it's fun):
---------------------------------
procedure TForm1.Button2Click(Sender: TObject);
var
PosL, PosT : Integer;
TLCap: string;
begin
Randomize;
// assign value to PosL and PosT variables
PosL:=Random(Form1.ClientWidth-Button2.Width);
PosT:=Random(Form1.ClientHeight-Button2.Height);
// assign value to TLCap variable
TLCap:='Position: ';
TLCap:=TLCap + IntToStr(Button2.Left) + ' - ';
TLCap:=TLCap + IntToStr(Button2.Top);
//use variables to change object properties
Form1.Caption:=TLCap;
with Button2 do begin
Left:=PosL;
Top:=PosT;
end
end;
----------------------------------
Constants
Constants (values that do not change during program execution) are declared using the const keyword. To declare a constant you don't need to specify a data type, but only assign an initial value.
----------------------------------
const
Min = 30;
Max = 500;
Middle = (Max + Min) div 2;
ErrStr = 'Some errors occured';
MaxAmount = 123.45;
-----------------------------------
Obviously, line of code like
-----------------------------------
Middle:= 275;
-----------------------------------
will generate an error! ("Left side cannot be assigned to")
However, following line of code if quite usual/acceptable:
-----------------------------------
SomeValue := Middle + 200;
-----------------------------------
Final notes
My idea was to show you some basics of Delphi's built in programming language. Object Pascal is far more complex and can give much more to Delphi developer. In some future articles I'll be writing about Loops and Decisions, Procedures, Functions, Arrays, Custom data types and so on...
_I_Play_Chess
10-19-2003, 11:18 AM
As you can see, a semicolon ( is always required to separate two simple statement.
As you can see, a semicolon ";" is always required to separate two simple statement.
_I_Play_Chess
10-19-2003, 11:25 AM
STRING TYPES IN DELPHI
-------------------------------
Delphi For Beginners:
Understanding and managing string data types in Delphi's Object Pascal. Learn about differences between Short, Long, Wide and null-terminated strings.
As like in any programming language, in Delphi, variables are placeholders used to store values; they have names and data types. The data type of a variable determines how the bits representing those values are stored in the computer's memory.
When we have a variable that will contain some array of characters, we can declare it to be of type String.
Delphi provides a healthy assortment of string operators, functions and procedures. Before assigning a String data type to a variable, we need to thorughly understand Delphi's four string types.
Short String
Simply put, Short String is a counted array of (ANSII) characters, with up to 255 characters in the string. The first byte of this array stores the length of the string. Since this was the main string type in Delphi 1 (16 bit Delphi), the only reason to use Short String is for backward compatibility.
To create a ShortString type variable we use:
-----------------------
var s: ShortString;
s := 'Delphi Programming';
//S_Length := Ord(s[0]));
//which is the same as Length(s)
----------------------
The s variable is a Short string variable capable of holding up to 256 characters, its memory is a statically allocated 256 bytes. Since this is usually wastefull - unlikely will your short string spread to the maximum length - second approach to using Short Strings is using subtypes of ShortString, whose maximum length is anywhere from 0 to 255.
---------------------
var ssmall: String[50];
ssmall := 'Short string, up to 50 characters';
---------------------
This creates a variable called ssmall whose maximum length is 50 characters.
Note: When we assign a value to a Short String variable, the string is truncated if it exceeds the maximum length for the type. When we pass short strings to some Delphi's string manipulationg routine, they are converted to and from long string.
String / Long / Ansi
Delphi 2 brought to Object Pascal Long String type. Long string (in Delphi's help AnsiString) represents a dynamically allocated string whose maximum length is limited only by available memory. All 32-bit Delphi versions use long strings by default. I recomend using long strings whenever you can.
--------------------
var s: String;
s := 'The s string can be of any size...';
--------------------
The s variable can hold from zero to any paractical number of characters. The string grows or shrinks as you assign new data to it.
We can use any string variable as an array of characters, the second character in s has the index 2. The following code
s[2]:='T';
assigns T to the second character os the s variable. Now the few of the first characters in s look like: TTe s str....
Don't be mislead, you can't use s[0] to see the length of the string, s is not ShortString.
Reference counting, copy-on-write
Since memory allocation is done by Delphi, we don't have to worry about garbage collection. When working with Long (Ansi) Strings Delphi uses reference counting. This way string copying is actually faster for long strings than for short strings.
Reference counting, by example:
---------------------
var s1,s2: String;
s1 := 'first string';
s2 := s1;
---------------------
When we create string s1 variable, and assign some value to it, Delphi allocates enough memory for the string. When we copy s1 to s2, Delphi does not copy the string value in memory, it ony increases the reference count and alters the s2 to point to the same memory location as s1.
To minimize copying when we pass strings to routines, Delphi uses copy-on-write techique. Suppose we are to change the value of the s2 string variable; Delphi copies the first string to a new memory location, since the change should affect only s2, not s1, and they are both pointing to the same memory location.
Wide String
Wide strings are also dynamically allocated and managed, but they don't use reference counting or the copy-on-write semantics. Wide strings consist of 16-bit Unicode characters.
About Unicode character sets
The ANSI character set used by Windows is a single-byte character set. Unicode stores each character in the character set in 2 bytes instead of 1. Some national languages use ideographic characters, which require more than the 256 characters supported by ANSI. With 16-bit notation we can represent 65,536 different characters. Indexing of multibyte strings is not reliable, since s[i] represents the ith byte (not necessarily the i-th character) in s.
If you must use Wide characters, you should declare a string variable to be of the WideString type and your character variable of the WideChar type. If you want to examine a wide string one character at a time, be sure to test for multibite characters. Delphi doesn't support automatic type conversions betwwen Ansi and Wide string types.
-----------------------
var s : WideString;
c : WideChar;
s := 'Delphi_ Guide';
s[8] := 'T';
//s='Delphi_TGuide';
-----------------------
Null terminated
A null or zero terminated string is an array of characers, indexed by an integer starting from zero. Since the array has no length indicator, Delphi uses the ASCII 0 (NULL; #0) character to mark the boundary of the string.
This means there is essentially no difference between a null-terminated string and an array[0..NumberOfChars] of type Char, where the end of the string is marked by #0.
We use null-terminated strings in Delphi when calling Windows API functions. Object Pascal lets us avoid messing arround with pointers to zero-based arrays when handling null-terminated strings by using the PChar type. Think of a PChar as being a pointer to a null-terminated string or to the array that represents one. For more info on pointers, check: Pointers in Delphi.
For example, The GetDriveType API function determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive. The following procedure lists all the drives and their types on a users computer. Place one Button and one Memo component on a form and asign an OnClick handler of a Button:
------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
Drive: Char;
DriveLetter: String[4];
begin
for Drive := 'A' to 'Z' do
begin
DriveLetter := Drive + ':\';
case GetDriveType(PChar(Drive + ':\')) of
DRIVE_REMOVABLE:
Memo1.Lines.Add(DriveLetter + ' Floppy Drive');
DRIVE_FIXED:
Memo1.Lines.Add(DriveLetter + ' Fixed Drive');
DRIVE_REMOTE:
Memo1.Lines.Add(DriveLetter + ' Network Drive');
DRIVE_CDROM:
Memo1.Lines.Add(DriveLetter + ' CD-ROM Drive');
DRIVE_RAMDISK:
Memo1.Lines.Add(DriveLetter + ' RAM Disk');
end;
end;
end;
--------------------------
Mixing Delphi's strings
We can freely mix all four different kinds of strings, Delphi will give it's best to make sence of what we are trying to do. The assignment s:=p, where s is a string variable and p is a PChar expression, copies a null-terminated string into a long string.
Character types
In addition to four string data types, Delphi has three character types: Char, AnsiChar, and WideChar. A string constant of length 1, such as 'T', can denote a character value. The generic character type is Char, which is equivalent to AnsiChar. WideChar values are 16-bit characters ordered according to the Unicode character set. The first 256 Unicode characters correspond to the ANSI characters.
_I_Play_Chess
10-19-2003, 11:36 AM
Title of the Correction in page 8 is:
"Chapter 6 page 8".
_I_Play_Chess
10-19-2003, 11:45 AM
ORDINAL DATA TYPES
---------------------------
Delphi For Beginners:
Extend Delphi's built-in types by constructing your own types.
Introduction to Types
Delphi's programming language is an example of a strongly typed language. This means that all variables must be of some type. A type is essentially a name for a kind of data. When we declare a variable we must specify its type, which determines the set of values the variable can hold and the operations that can be performed on it.
Many of Delphi's built-in data types, such as Integer or String, can be refined or combined to create new data types. In this article we'll see how to create custom ordinal data types in Delphi.
Ordinal types
The defining characteristics of ordinal data types are: they must consist of a finitive number of elements and they must be ordered in some way.
The most common examples of ordinal daty types are all the Integer types as well as Char and Boolean type. More preciselly, Object Pascal has twelve predefined ordinal types: Integer, Shortint, Smallint, Longint, Byte, Word, Cardinal, Boolean, ByteBool, WordBool, LongBool, and Char. There are also two other classes of user-defined ordinal types: enumerated types and subrange types.
In any ordinal types, it must make sense to move backward or forward to the next element. For example, real types are not ordinal because moving backward or forward doesn't make sense: the question "What is the next real after 2.5?" is meaningless.
Since, by definition, each value except the first has a unique predecessor and each value except the last has a unique successor, several predefined function are used when working with ordinal types:
Function -Effect :
Ord(X) -Gives the index of the lement
__________________________________________________ __
Pred(X) -Goes to the element listed before X in the type
__________________________________________________ __
Succ(X) -Goes to the element listed after X in the type
__________________________________________________ __
Dec(X;n) -Moves n elements back (if n is omitted moves 1 element back)
__________________________________________________ __
Inc(X;n) -Moves n elements forward (if n is omitted moves 1 element forward)
__________________________________________________ __
High(X) -Returns the lowest value in the range of the ordinal data type X.
__________________________________________________ __
Low(X) -Returns the highest value in the range of the ordinal data type X.
__________________________________________________ __
For example, High(Byte) returns 255 because the highest value of type Byte is 255, and Succ(2) returns 3 because 3 is the successor of 2.
Note: If we try to use Succ when at the last element Delphi will generate a run-time exception if the range checking is on.
Enumerated Data Types
The easiest way to create a new example of an ordinal type is simply to list a bunch of elements in some order. The values have no inherent meaning, and their ordinality follows the sequence in which the identifiers are listed. In other words, an enumeration is a list of values.
------------------------
type TWeekDays = (Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday, Sunday);
------------------------
Once we define an enumerated data type, we can declare variables to be of that type:
------------------------
var SomeDay : TWeekDays;
------------------------
The primary purpose of an enumerated data type is to make clear what data your program will manipulate. An enumerated type is really just a shorthand way of assigning sequential values to constants. Given these declarations, Tuesday is a constant of type TWeekDays.
Delphi allows us to work with the elements in an enumerated type using an index that comes from the order that they were listed in. In the previous example: Monday in the TWeekDays type declaration has the index 0, Tuesday has the index 1, and so on. The functions listed in the table before let us, for example, use Succ(Friday) to "go to" Saturday.
Now we can try something like:
------------------------
for SomeDay := Monday to Sunday do
if SomeDay = Tuesday then
ShowMessage('Tuesday it is!');
------------------------
The Delphi Visual Component Library uses enumerated types in many places. For example, the position of a form is defined as follows:
------------------------
TPosition = (poDesigned, poDefault, poDefaultPosOnly,
poDefaultSizeOnly, poScreenCenter);
------------------------
We use Position (through the Object Inspector) to get or set the size and placement of the form.
Subrange Types
Simply put, a subrange type represents a subset of the values in another ordinal type. In general, we can define any subrange by starting with any ordinal type (including a previously defined enumerated type) and using a double dot:
------------------------
type TWorkDays = Monday .. Friday;
------------------------
Here TWorkDays includes the values Monday, Tuesday, Wednesday, Thursday and Friday.
_I_Play_Chess
10-19-2003, 11:50 AM
Ord(X) -Gives the index of the lement
Should read:
Ord(X) -Gives the index of the "element ".
_I_Play_Chess
10-19-2003, 11:59 AM
ARRAYS IN OBJECT PASCAL
----------------------------------
Delphi For Beginners:
Understanding and using array data types in Delphi.
Array := Series of Values
The concept of arrays in Object pascal is simple: arrays allow us to refer to a series of variables by the same name and to use a number (an index) to tell them apart. Arrays have both upper and lower bounds, and the elements of the array are contiguous within those bounds.
Elements of the array are values that are all of the same type
(string, integer, real).
In Delphi, there are two types of arrays: a fixed-size array which always remains the same size - static array, and a dynamic array whose size can change at run-time.
Static Arrays
Suppose we are writing a program that lets a user enter some values (e.g. the number of appointments) at the beginning of each day. We would choose to store the information in a list. We could call this list Appointments, and each number might be stored as Appointments[1], Appointments[2], an so on.
To use the list, we must first declare it. For example:
------------------------
var Appointments : array[0..6] of Integer;
------------------------
declares a variable called Appointments that holds an one-dimensional array (vector) of 7 integer values. Given this declaration, Appointments[3] denotes the third integer value in Appointments. The number in the brackets is called the index.
If we create a static array but don’t assign values to all its elements, the unused elements contain random data; they are like uninitialized variables. The following code can be used to set all elements in the Appointments array to 0.
-------------------------
for i := 0 to 6 do Appointments[i] := 0;
-------------------------
Sometimes we need to keep track of related information in an array. For example, to keep track of each pixel on your computer screen, you need to refer to its X and Y coordinates. This can be done using a multidimensional array to store the values.
With Delphi, we can declare arrays of multiple dimensions. For example, the following statement declares a two-dimensional 7 by 24 array:
------------------------
var DayHour : array[1..7, 1..24] of Real;
------------------------
To compute the number of elements in a multidimensional array, multiply the number of indexes. The DayHour variable, declared above, sets aside 168 (7*24) elements, in 7 rows and 24 columns.
To retreive the value from cell in the third row and seventh column we would use: DayHour[3,7] or DayHour[3][7]. The following code can be used to set all elements in the DayHour array to 0.
--------------------------
for i := 1 to 7 do
for j := 1 to 24 do
DayHour[i,j] := 0;
-------------------------
Dynamic Arrays
Sometimes you may not know exactly how large to make an array. You may want to have the capability of changing the size of the array at run time.
Delphi 4 introduced the concept of dynamic arrays in Object Pascal. A dynamic array declares its type, but not its size. The actual size of a dynamic array can be changed at run time by the use of the SetLength procedure.
For example, the following variable declaration
-----------------------------
var Students : array of string;
-----------------------------
creates a one-dimensional dynamic array of strings.
The declaration does not allocate memory for Students. To create the array in memory, we call SetLength procedure. For example, given the declaration above,
---------------------------
SetLength(Students, 14);
---------------------------
allocates an array of 14 strings, indexed 0 to 13. Dynamic arrays are always integer-indexed, always starting from 0 to one less than their size in elements.
To create a two-dimensional dynamic array, use the following code:
--------------------------
var Matrix: array of array of Double;
begin
SetLength(Matrix, 10, 20)
end;
--------------------------
which allocates space for a two-dimensional, 10 x 20, array of Double floating-point values.
Note: To remove a dynamic array's memory space we assign nil to the array variable, like:
Matrix := nil;
Very often, your program doesn't know at compile time how many elements will be needed, that number will not be known until runtime. With dynamic arrays you can allocate only as much storage as is required at a given time. In other words, the size of dynamic arrays can be changed at run time, which is one of the key advantages to dynamic arrays. The next code creates an array of integer values and then calls the Copy function to resize the array.
------------------------
var Vector: array of Integer;
i : integer;
begin
SetLength(Vector, 10);
for i := Low(Vector) to High(Vector) do
Vector[i] := i*10;
...
//now we need more space
SetLength(Vector, 20);
//here, Vector array can hold up to 20 elements
//(it already has 10 of them)
end;
------------------------
Note 1: SetLength function creates a larger (or smaller) array, and copies the existing values to the new array.
Note 2: The Low and High functions ensure you access every array element without looking back in your code for the correct lower and upper index values.
_I_Play_Chess
10-19-2003, 12:16 PM
Records in Delphi - Part 1
--------------------------------
Learn about records, Delphi's Pascal data structure that can mix any of Delphi's built in types including any types you have created.
Records
Sets are ok, arrays are great. Suppose we want to create three one-dimensional arrays for 50 members in our programming community. The first array is for names, the second for e-mails, and the third for number of uploads (components or applications) to our community. Each array (list) would have matching indexes and plenty of code to maintain all three lists in parallel. Of course, we could try with one three-dimensional array, but what about it's type? We need string for names and e-mails, but an integer for the number of uploads.
The way to work with such a data structure is to use Object Pascal's record structure.
An example
For example, the following declaration creates a record type called TMember, the one we could use in our case.
-------------------------
type
TMember = record
Name : string;
eMail : string;
Posts : Cardinal;
end;
-------------------------
Essentially, a record data structure can mix any of Delphi's built in types including any types you have created. Record types define fixed collections of items of different types. Each item, or field, is like a variable, consisting of a name and a type.
TMember type contains three fields: a string value called Name (to hold the name of a member), a value of a string type called eMail (for one e-mail), and an integer (Cardinal) called Posts (to hold the number of submitions to our community).
Once we have set up the record type, we can declare a variable to be of type TMember. TMember is now just as good variable type for variables as any of Delphi's built in types like String or Integer. Note: the TMember type declaration, does not allocate any memory for the Name, eMail, and Posts fields;
To actually create an instance of TMember record we have to declare a variable of TMember type, as in the following code:
--------------------------
var DelphiGuide, AMember : TMember;
--------------------------
Now, when we have a record, we use a dot to isolate the fields of DelphiGuide:
------------------------
DelphiGuide.Name := 'Delphi Community';
DelphiGuide.eMail := 'delphi.guide@limpkimw.com';
DelphiGuide.Posts := 15;
------------------------
Note: the above piece of code could be rewritten with the use of with keyword:
------------------------
with DelphiGuide do begin
Name := '_i_play_chess';
eMail := 'delphi.guide@limpkimw.com';
Posts := 15;
end;
------------------------
We can now copy the values of DelphiGuide’s fields to AMember:
------------------------
AMember := DelphiGuide;
------------------------
Scope and visibility
Record type declared within the declaration of a form (implementation section), function, or procedure has a scope limited to the block in which it is declared. If the record is declared in the interface section of a unit it has a scope that includes any other units or programs that use the unit where the declaration occurs.
To learn more about Object Pascal variable scope go the Variable Scope article.
In arrays
Since TMember acts like any other Object Pascal type, we can declare an array of record variables:
----------------------
var DPMembers : array[1..50] of TMember;
----------------------
To access the fifth member we use:
----------------------
with DPMembers[4] do begin
Name := 'Pugmisish';
eMail := 'pugmisish@domain.com'
Posts := 0;
end;
----------------------
Or, to display information (e-mail, for example) about every member we could use:
-----------------------
var i: cardinal;
for i:= 1 to 50 do
ShowMessage(DPMembers[i].eMail);
-----------------------
Records as fields
Since a record type is legitimate as any other Delphi type, we can have a field of a record be a record itself. For example, we could create ExpandedMember to keep track of what the member is submitting along with the member information:
------------------------
type
TExpandedMember = record
SubmitType : string;
Member : TMember;
end;
------------------------
Filling out all the information needed for a single record is now somehow harder. More periods (dots) are required to access the fields of TExpandedMember:
------------------------
var SubTypeMember :TExpandedMember;
SubTypeMember.SubmitType:='VCL';
SubTypeMember.Member.Name:='vcl Programmer';
SubTypeMember.Member.eMail:='vcl@about.com';
SubTypeMember.Member.Name:=555;
------------------------
Final words
A record type can have a variant part (I don't mean Variant type variable). Variant records are used, for example, when we want to create a record type that has fields for different kinds of data, but we know that we will never need to use all of the fields in a single record instance.
The use of a variant record type is not type-safe and is not a recommended programming practice, particularly for beginners.
However, variant records can be quite usefull, if you ever find yourself in a situation to use them, here's the second part of this article: "Records in Delphi - Part 2" next.
_I_Play_Chess
10-19-2003, 01:02 PM
Records in Delphi - Part 2
--------------------------------
Why and when to use variant records, plus creating an array of records.
Article submitted by: Kevin S. Gallagher
-------------------------------------------------
A record is a special kind of user-defined data type. A record is a container for a mixture of related variables of diverse types, referred to as fields, collected into one type. Records are sometimes called complex types, because they are made up of other data types. Other data types by comparison, are often referred to as simple data types.
Essentially, a record data structure can mix any of Delphi's built in types including any types you have created.
Record Types
I can hear beginners saying "I really don't have a use for them…" or "I will learn them later when I am not so busy". Well, later is not always the best time to learn things, especially when "later" may be crunch time when unwelcome bugs habitually creep into applications!
Records are commonly used in Microsoft Windows API calls, where they are referred to as "structures", which is C++ programming language terminology for a very similar thing.
Suppose you are writing an application and you need to determine a form's original state before minimizing or maximizing the form, or get/set the size a form can shrink to or grow to. Some of this can be done with plain old Delphi code while other parts need to be done using API calls. If you need to restrict form sizing then you are going to have to use WM_GETMINMAXINFO from the API. As you might have guessed, that uses a record. In Delphi Win32 Help you will find that the record used is defined as:
------------------------------
typedef struct tagMINMAXINFO ** // mmi
POINT ptReserved;
POINT ptMaxSize;
POINT ptMaxPosition;
POINT ptMinTrackSize;
POINT ptMaxTrackSize;
} MINMAXINFO;
-------------------------------
The Delphi architects thoughtfully wrote the interface translation to handle this, but you need to search though the Delphi source code to find the information. The following record is defined in Messages.pas:
----------------------------
TWMGetMinMaxInfo = record
Msg: Cardinal;
Unused: Integer;
MinMaxInfo: PMinMaxInfo;
Result: Longint;
end;
----------------------------
The rest is found in the Windows unit:
----------------------------
type
** Struct pointed to by WM_GETMINMAXINFO lParam }
PMinMaxInfo = ^TMinMaxInfo;
TMinMaxInfo = packed record
ptReserved: TPoint;
ptMaxSize: TPoint;
ptMaxPosition: TPoint;
ptMinTrackSize: TPoint;
ptMaxTrackSize: TPoint;
end;
----------------------------
Of course you could say that not much knowledge is needed for accomplishing the task we set up, but what if Delphi didn't have code for the above? Obviously you would have had to write it yourself, and without the proper knowledge it would be impossible to code. At times you need many pieces of information about a form. One of the API calls to acquire the information is called GetWindowPlacement, and to change form stuff that Delphi does not directly handle you might need to call SetWindowPlacement. Both require the use of this record:
---------------------------
typedef struct _WINDOWPLACEMENT ** // wndpl
UINT length;
UINT flags;
UINT showCmd;
POINT ptMinPosition;
POINT ptMaxPosition;
RECT rcNormalPosition;
} WINDOWPLACEMENT;
---------------------------
Delphi has defined it, but if they had not, we would be translating it ourselves!
---------------------------
Tkg_WINDOWPLACEMENT = Record
Length : Integer;
Flags : Integer;
ShowCmd : Integer;
ptMinPosition : TPoint;
ptMaxPosition : TPoint;
rcNormalPosition : TRect;
end;
---------------------------
Variant records
This demonstrates the importance of knowing how to work with records for API calls. While everyday cases are not obvious, they do arise. One is hinted at in Delphi Help, with an example:
---------------------------
TPerson = record
FirstName : string[40];
LastName : string[40];
** Fixed portion of record begins here }
BirthDate: TDate;
case
Citizen: Boolean of
** variant portion of record begins here }
True: (BirthPlace: string[40]);
False:
(
Country: string[20];
EntryPort: string[20];
EntryDate: TDate;
ExitDate: TDate
);
end;
----------------------------
As you can see, record type can have a variant part, which looks like a case statement. The first part of the declaration - up to the reserved word case - is the same as that of a standard record type. The remainder of the declaration - from case to the optional final semicolon - is called the variant part.
The above variant record declaration has a section (which must follow the fixed section) that can have multiple personalities. In the above example,
if Citizen equaled true then we would have:
-----------------------
TPerson = record
FirstName, LastName: string[40];
BirthDate: TDate;
BirthPlace: string[40];
end;
-----------------------
If Citizen was false:
-----------------------
TPerson = record
FirstName, LastName: string[40];
BirthDate: TDate;
Country: string[20];
EntryPort: string[20];
EntryDate: TDate;
ExitDate: TDate;
end;
-----------------------
Stacking Records into An Array
Now we have a understanding of records, let's step into another dimension and create an array of records which allows you to store multiple records which can be returned to a calling form. Place the following declaration into the interface section of a form.
--------------------------
TPerson = record
FirstName, LastName: string[40] ;
BirthDate: TDate ;
BirthPlace: string[40] ;
end;
--------------------------
Add a Memo control and a button to the form, enter the code below:
--------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
MyPeople: Array[0..2] of TPerson ;
i:Integer ;
begin
Memo1.Clear ;
for i := 0 to 2 do
begin
MyPeople[i].FirstName := 'MyPeople[' + IntToStr(i)+ '].FirstName' ;
MyPeople[i].LastName := 'MyPeople[' + IntToStr(i) + '].LastName';
MyPeople[i].BirthDate := Now;
MyPeople[i].BirthPlace := 'MyPeople[' + IntToStr(i) + '].BirthPlace';
end ;
for i := 0 to 2 do
begin
with Memo1.Lines do
begin
Add(MyPeople[i].FirstName + ' ' + MyPeople[i].LastName);
Add(DateToStr(MyPeople[i].BirthDate));
Add(MyPeople[i].BirthPlace);
Add('');
end;
end;
end;
--------------------------------
Pressing the button populates the array of records with dummy information, then displays the records in the memo control. This should give you a starting point to working with record arrays. The main thing to remember about the example above is that we created a record type, supplied the name of TPerson, then created a local variable called MyPeople which is an array of type TPerson which can hold three (3) rows of information.
That's it for now.
To know how to put that code and compile
it :
---------------------------------------
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
** Private declarations }
public
** Public declarations }
end;
type
TPerson = record
FirstName, LastName: string[40] ;
BirthDate: TDate ;
BirthPlace: string[40] ;
end;
var
Form1: TForm1;
implementation
**$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
MyPeople: Array[0..2] of TPerson ;
i:Integer ;
begin
Memo1.Clear ;
for i := 0 to 2 do
begin
MyPeople[i].FirstName := 'MyPeople[' + IntToStr(i) + '].FirstName' ;
MyPeople[i].LastName := 'MyPeople[' + IntToStr(i) + '].LastName';
MyPeople[i].BirthDate := Now;
MyPeople[i].BirthPlace := 'MyPeople[' + IntToStr(i) + '].BirthPlace';
end ;
for i := 0 to 2 do
begin
with Memo1.Lines do
begin
Add(MyPeople[i].FirstName + ' ' + MyPeople[i].LastName);
Add(DateToStr(MyPeople[i].BirthDate));
Add(MyPeople[i].BirthPlace);
Add('');
end;
end;
end;
end.
----------------------------------------------
Here what u should see:
_I_Play_Chess
10-19-2003, 01:36 PM
Pointers in Delphi
----------------------
An introduction to pointer data type in Delphi. What are pointers, why, when and how to use them.
One of the things beginners in Delphi (and programming in general) find most difficult to understand is the concept of pointers. The purpose of this article is to provide an introduction to pointers and their use to Delphi beginners.
Even though pointers are not so important in Delphi as the are in C or C++, pointers are such a "basic" tool that almost anything having to do with programming must deal with pointers in some fashion. For that reason, you will read that "a string is really just a pointer" or that "an object is really just a pointer" or "event handler such as OnClick is actually a pointer to a procedure".
Pointers can be a very powerful aspect of a language. Unfortunately, pointers are also frequently behind much of the complexity of the languages that use them. The Object Pascal language of Delphi took a big step toward removing the complexity of pointers by hiding them in many cases.
Pointer as data type
So what is a pointer? Simply put, a pointer is a variable that holds the address of anything in memory.
To concrete this definition, just keep in mind the following: everything used in an application is stored somewhere in the computer's memory, a pointer to that item simply points the program to that memory location. Because a pointer holds the address of another variable, it is said to point to that variable.
We can consider a pointer as a means to hold a data address at runtime. It can point to different variables in turn, even to unnamed variables which are alive only for a particular run-time period.
As with all Delphi types, we must declare a pointer before we can use it. Most of the time pointers in Object Pascal point to a specific type:
---------------------------------
var
iValue, j : integer;
pIntValue : ^integer;
begin
iValue := 2001;
pIntValue := @iValue;
...
j:= pIntValue^;
end;
---------------------------------
The syntax to declare a pointer data type uses a caret (^). In the code above iValue is an integer type variable and pIntValue is an integer type pointer. Since a pointer is nothing more than an address in memory, we must assign to it the location (address) of value stored in iValue integer variable. The @ operator returns the address of a variable (or a function or procedure as will be seen later in this article). Equivalent to the @ operator is the Addr function. Note that pIntValue's value is not 2001.
In the code above the pIntValue is a typed integer pointer. Good programming style is to use typed pointers as much as you can. The Pointer data type is a generic pointer type - represents a pointer to any data.
Note that when "^" appears after a pointer variable it dereferences the pointer; that is, it returns the value stored at the memory address held by the pointer. In the code above (after it) variable j has the same value as iValue. It might look like this has no purpose when we can simply assign iValue to j, but this piece of code lies behind most calls to Win API.
NILing pointers
Unassigned pointers are dangerous. Since pointers let us work directly with computer's memory, if we try to (by mistake) write to a protected location in memory we could get a access violation error. This is the reason why we should always initialize a pointer to a special value of NIL. The reserved word nil is a special constant that can be assigned to any pointer. When nil is assigned to a pointer, the pointer doesn’t reference anything. Delphi presents, for example, an empty dynamic array or a long string as a nil pointer.
Character pointers
The fundamental types PAnsiChar and PWideChar represent pointers to AnsiChar and WideChar values. The generic PChar represents a pointer to a Char variable. These character pointers are used to manipulate null-terminated strings. Think of a PChar as being a pointer to a null-terminated string or to the array that represents one. For more on null-terminated strings go see: "String types in Delphi". Have in mind that long-string variables are implicitly pointers.
Pointers to records
When we define a record or other data type, it's a common practice also to define a pointer to that type. This makes it easy to manipulate instances of the type without copying large blocks of memory. In fact, any data type that requires large, dynamically allocated blocks of memory uses pointers.
The ability to have pointers to records (and arrays) makes it much more easier to set up complicated data structures as linked lists and trees. What follows is an example of the type declaration for a simple linked list:
------------------------
type
pNextItem = ^LinkedListItem
LinkedListItem = record
sName : String;
iValue : Integer;
NextItem : pNextItem;
end;
------------------------
The idea behind linked lists is to give us the possibility to store the address to the next linked item in a list inside a NextItem record field. For more on data structures consider the book: "The Tomes of Delphi: Algorithms and Data Structures".
Procedural and method pointers
Another important pointer concept in Delphi are procedure and method pointers. Pointers that point to the address of a procedure or function are called procedural pointers. Method pointers are similar to procedure pointers. However, instead of pointing to stand-alone procedures, they must point to class methods. Method pointer is a pointer that contains information about the name of the method that is being invoked as well as the object that is being invoked. To see some function pointer in action go see: "Dynamic World of Packages". We'll talk more about such pointers in some of the future articles.
Pointers and Windows API
The most common use for pointers, in Object Pascal, is interfacing to C and C++ code, which includes accessing the Windows API. Windows API functions use a number of data types that may be unfamiliar to the Delphi programmer. Most of the parameters in calling API functions are pointers to some data type. As stated above, we use null-terminated strings in Delphi when calling Windows API functions. In many cases when an API call returns a value in a buffer or a pointer to a data structure, these buffers and data structures must be allocated by the application before the API call is made.
For example, take a look at the SHBrowseForFolder Windows API function. This function is used to invoke a Windows system dialog used to browse for files and folders on users hard drive as well as network computers and printers.
Pointer and memory allocation
The real power of pointers comes from the ability to set aside memory while the program is executing. I would not like to bother you with heaps and memory programming, for now the next piece of code should be enough to prove that working with pointers is not so hard as it may seem.
The following code is used to change the text (caption) of the control whose Handle is provided.
------------------------------
procedure GetTextFromHandle(hWND: THandle);
var
pText : PChar; //a pointer to char (see above)
TextLen : integer;
begin
{get the length of the text}
TextLen:=GetWindowTextLength(hWND);
{alocate memory}
GetMem(pText,TextLen); // takes a pointer
{get the control's text}
GetWindowText(hWND, pText, TextLen + 1);
{display the text}
ShowMessage(String(pText))
{free the memory}
FreeMem(pText);
end;
-------------------------------
_I_Play_Chess
10-19-2003, 01:41 PM
RECURSIONS IN DELPHI
------------------------------
Writing and using recursive functions in Object Pascal.
Recursions in general
Recursion is a very simple, yet useful and powerful programmer's tool. As we know, subroutines can, and frequently do, call other subroutines. A subroutine that activates/calls itself is called recursive. Recursion is a general method of solving problems by reducing them to simpler problems of a similar type. A recursive subroutine constantly calls itself, each time in a simpler situation, until it gets to the trivial case, at which point it stops.
Many programmers often avoid this type of subroutine because it can be confusing and complicated. This article is going to make recursion in Objet Pascal simple ... I hope.
Recursions in Delphi
In Delphi, there are actually two types of recursions possible. In the first, the subroutine only calls itself. This is called direct recursion. Using direct recursion in Delphi is simple, just call the subroutine the way you would call any other. The second type is called mutual recursion. Mutual recursion occurs when routine A calls routine B, and then while routine B is running, routine B calls routine A.
Direct recursions
The general framework for a recursive solution to a problem looks like
Solve recursively (problem)
If the problem is trivial, do the obvious
Simplify the problem
Solve recursively (simplified problem)
What follows are some of the recursive function examples:
Factorial
"If the integer number is less than zero, reject it. If the number is zero or one, its factorial is one. If the number is larger than one, multiply it by the factorial of the next smaller number."
In other words: Fact(n) : = n Fact(n-1) if n>1 otherwise Fact(n):=1.
function Fact(inbr:integer):integer;
begin
if inbr < 1 then Result := 1
else Result := inbr * Fact(inbr-1);
end;
Greatest common divisor
In mathematics, GCD or greatest common divisor is defined as the largest number that divides both of two given integer numbers. Around 2,000 years ago, Euclid gave the following method of computing the GCD of two integers, a and b:
If b divides a, then the GCD is a.
Otherwise GCD(a,b):=GCD(b, a mod b)
Where the mod function gives the reminder after integer division.
function GCD(a,b : integer):integer;
begin
if (b mod a) = 0 then Result := a
else Result := GCD(b, a mod b);
end;
Exponents
The problem is: how to calculate mN if N is some positive integer number.
function iPow(base, exp: integer): integer;
begin
if exp = 0 then Result := 1
else Result := base * iPow(base, exp - 1);
end;
Recursive file search
When looking for files, it is often useful (necessary) to search through subfolders. In the Searching for Files article you can see how to use Object Pascal's strength to create a simple, but powerfull, find-all-matching-files project.
Mutual recursions
We know that in Object Pascal we can't use an identifier until we have declared it. In other words, Delphi doesn't let one routine call another routine that is defined after it. To make such mutual recursion possible, we need to declare a routine as a forward declaration. To make a forward declaration, add the forward reserved word at the end of the header of the subroutine. Then place the header with the extra forward declaration before the code for the subroutine that will call it. Here's an example of using a forward declaration:
--------------------------
procedure MyProc1(dummy : integer); forward;
procedure MyProc2;
begin
MyProc1(5);
end;
--------------------------
procedure MyProc1(dummy : integer);
var i:integer;
begin
for i:= 1 to dummy do
showmessage(IntToStr(i));
end;
--------------------------
Final words
Although recursion looks confusing the first time through, spend some time reviewing those code examples and in no time you won't know how you worked without it.
It is extremely important to design recursive functions with great care. If you even suspect that there is any chance of an infinite recursion, you can have the function count the number of times it calls itself, and thus make sure that if the function calls itself too many times, however many you decide that should be, it automatically quits.
_I_Play_Chess
10-19-2003, 02:07 PM
Delphi Code Puzzles
--------------------------
Welcome to the Delphi Code Puzzle solving challenge, the place where you compete against yourself and the Delphi compiler for fame and glory.
Delphi Code Puzzles Description
Your task is to solve Delphi Code Puzzle by assembling a program code one line at a time. For each code line, your will be presented with four options. Each option represents a line of code. Only one line will be the correct pick - sometimes even more of them. You'll know if you've picked the correct line only when you finish the puzzle.
All the puzzles are designed to work as console mode applications.
(See NO GUI Delphi applications :
Page 1: Setting up a console mode application with Delphi.
Page 2: A console mode Delphi example; Cosole applications: Tips & Tricks in page 16 part1 and part 2)
------------------------------------------------------------------------------
In other words, if you need an input from the "user" you use the readln() function. If you need to write something back to the user you use the writeln() function.
When you reach to the end - by solving a particular Delphi Pascal code puzzle, you'll have the option to see whether your code can be compiled in Delphi, or not.
If the code is correct and, therefore, can be compiled, BRAVO, you've solved the puzzle.
Delphi Code Puzzles
Here's a list of available Delphi Code Puzzles, try them - test your Delphi Programming knowledge for free!
Delphi Code Puzzle: 001 as example
----------------------------------------------
Problem: Let the user type it's name in a string variable sName. The program should output 'Hello Delphi Programming World, *sName*'.
Puzzle Solving Description
This problem should be solved by using only three lines of Delphi Pascal code.
Your task is to solve this code puzzle by assembling a program code one line at a time. For each code line, your will be presented with four options. Each option represents a Delphi Pascal command. Only one line will be the correct pick - sometimes even more of them. You'll know if you've picked the correct line only when finish the puzzle.
When you reach to the end - by solving this Delphi Pascal code puzzle, you'll have the option to see whether your code can be compiled in Delphi, or not.
If the code is correct and, therefore, can be compiled, BRAVO, you've solved the puzzle.
The solved code puzzle
This code puzle is designed to help you understand how all the code puzzles work. Here goes the code for the solved puzzle.
Puzzle 001 solved
-----------------------
Declarations:
var
sName: string;
Code:
line 1: writeln('Please enter your name:');
line 2: readln(sName);
line 3: writeln('Hello Delphi Programming World, from ' + sName);
_I_Play_Chess
10-19-2003, 02:12 PM
NO GUI Delphi applications
----------------------------------
Page 1: Setting up a console mode application with Delphi.
Console applications are pure 32-bit Windows programs that run without a graphical interface. When a console application is started, Windows creates a text-mode console window through which the user can interact with the application. These applications typically don't require much user input. All the information a console application needs can be provided through command line parameters.
For students, console applications will simplify learning Pascal and Delphi - after all, all the Pascal introductory examples are just console applications.
New ... Console application
Here's how to quickly build console applications that run without a graphical interface.
If you have a Delphi version newer than 4, than all you have to do is to use the Console Application Wizard. Delphi 5 introduced the console application wizard. You can reach it by pointing to File|New, this opens up a New Items dialog - in the New page select the Console Application. Note that in Delphi 6 the icon that represents a console application looks different. Double click the icon and the wizard will setup a Delphi project ready to be compiled as a console application.
While you could create console mode applications in all 32-bit versions of Delphi, it's not an obvious process. Let's see what you need to do in Delphi versions <=4 to create an "empty" console project. When you start Delphi, a new project with one empty form is created by default. You have to remove this form (a GUI element) and tell Delphi that you want a console mode app. This is what you should do:
0. Select "File | New Application"
1. Select "Project | Remove From Project..."
2. Select Unit1 (Form1) and click OK. Delphi will remove the selected unit from the uses clause of the current project.
3. Select "Project | View Source"
4. Edit your project source file:
• Delete all the code inside "begin" and "end".
• After the uses keyword, replace the "Forms" unit with "SysUtils".
• Place **$APPTYPE CONSOLE} right under the "program" statement.
You are now left with a very small program which looks much like a Turbo Pascal program which, if you compile it will produce a very small EXE. Note that a Delphi console program is not a DOS program because it is able to call Windows API functions and also use its own resources. No matter how you have created a skeleton for a console application your editor should look like:
This is nothing more than a "standard" Delphi project file, the one with the .dpr extension.
. The program keyword identifies this unit as a program's main source unit. When we run a project file from the IDE, Delphi uses the name of the Project file for the name of the EXE file that it creates - Delphi gives the project a default name until you save the project with a more meaningful name.
. The $APPTYPE directive controls whether to generate a Win32 console or graphical UI application. The **$APPTYPE CONSOLE} directive (equivalent to the /CC command-line option), tells the compiler to generate a console application.
. The uses keyword, as usual, lists all the units this unit uses (units that are part of a project). As you can see, the SysUtils unit is included by default. Another unit is included too, the System unit, though this is hidden from us.
. In between the begin ... end pair you add your code.
Ok. Now when you know how a console application looks like it's time for a real example. Let's create a simple game!
_I_Play_Chess
10-19-2003, 02:44 PM
NO GUI Delphi applications
----------------------------------
Page 2: A console mode Delphi example; Cosole applications: Tips & Tricks
Guess A Number
Let's create a simple console game. The computer will randomly pick an integer number from 0 to 50. Your task is to guess the number. Each time you pick a number a program will tell that your number is larger or smaller than the one you are looking for. When, finally, you find what the number was it'll tell you how many times it took you to find it.
I'll give you the code, but first let's see what are two most seen commands in a console application:
Write and Read RTL procedures are typically used for writing and reading from a file. There are two standard text-file variables, Input and Output. In a Console application, Delphi automatically associates the Input and Output files with the application's console window. The standard file variable Input is a read-only file associated with the operating system's standard input (typically the keyboard). The standard file variable Output is a write-only file associated with the operating system's standard output (typically the display).
Thus, Writeln is used to display a message; ReadLn is normally used to read in variables. In the code below, you will notice it ends with a ReadLn. Readln without any parameters simply waits for the [Enter] key to be pressed. One of the peculiarities of a console application is that when it has stopped running, the console window is automatically closed. The ReadLn statement is necessary so that the user can see any text produced by Writeln statements before it disappears off the screen when the program finishes.
Here goes the code. I hope you understand it.
-------------------------------
program GuessANumber;
**$APPTYPE CONSOLE}
uses
SysUtils;
var
rn, un, cnt: Integer;
guess: Boolean;
begin
Randomize;
rn := Trunc(Random(50) + 1);
Write('Computer has picked an integer number,');
Write('from 1 to 50, guess the number!');
WriteLn('Your guess is: ');
cnt := 1; un := 0;
Guess := False;
while Guess = False do begin
ReadLn(un);
if un > rn then
Write('Wrong, gimme a smaller number: ')
else if un < rn then
Write('Wrong, gimme a larger number: ')
else //un=rn
begin
Guess:=True;
Writeln;
Write('Correct! It took you ' +
IntToStr(cnt) +
' times to guess!')
end;
cnt := cnt + 1;
end; //while
ReadLn; //don't close the window, wait for [Enter]
end.
=======================
Simply run the project and play....
(See the picture 1 below attached:)
Console applications: Tips and Tricks
Since Console applications are not standard Delphi projects, you should be familiar with several situations that evolve when working with console windows.
Here's how to Capture the Output From a Console mode application and how to Determine the output of a console application.
(some examples:)
Capture the Output From a DOS Window
==============================
**
The example runs 'chkdsk.exe c:\' and
displays the output to Memo1
Put a TMemo (Memo1) and a TButton (Button1)
in your form. Put this code in the
OnCLick event procedure of Button1:
}
procedure TForm1.Button1Click(Sender: TObject);
procedure RunDosInMemo(DosApp:String;AMemo:TMemo);
const
ReadBuffer = 2400;
var
Security : TSecurityAttributes;
ReadPipe,WritePipe : THandle;
start : TStartUpInfo;
ProcessInfo : TProcessInformation;
Buffer : Pchar;
BytesRead : DWord;
Apprunning : DWord;
begin
With Security do begin
nlength := SizeOf(TSecurityAttributes);
binherithandle := true;
lpsecuritydescriptor := nil;
end;
if Createpipe (ReadPipe, WritePipe,
@Security, 0) then begin
Buffer := AllocMem(ReadBuffer + 1);
FillChar(Start,Sizeof(Start),#0);
start.cb := SizeOf(start);
start.hStdOutput := WritePipe;
start.hStdInput := ReadPipe;
start.dwFlags := STARTF_USESTDHANDLES +
STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
if CreateProcess(nil,
PChar(DosApp),
@Security,
@Security,
true,
NORMAL_PRIORITY_CLASS,
nil,
nil,
start,
ProcessInfo)
then
begin
repeat
Apprunning := WaitForSingleObject
(ProcessInfo.hProcess,100);
Application.ProcessMessages;
until (Apprunning <> WAIT_TIMEOUT);
Repeat
BytesRead := 0;
ReadFile(ReadPipe,Buffer[0],
ReadBuffer,BytesRead,nil);
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer,Buffer);
AMemo.Text := AMemo.text + String(Buffer);
until (BytesRead < ReadBuffer);
end;
FreeMem(Buffer);
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
CloseHandle(ReadPipe);
CloseHandle(WritePipe);
end;
end;
begin {button 1 code}
RunDosInMemo('chkdsk.exe c:\',Memo1);
end;
Exit From Windows
==================
//reboot windows
ExitWindowsEx(EWX_REBOOT,0);
//shut down windows
ExitWindowsEx(EWX_SHUTDOWN,0);
// log off and prompt for login
ExitWindowsEx(EWX_LOGOFF,0);
How to determine the output of a console application
========================================
**
When you write a console mode application (no GUI),
the output by default goes to the screen, but this
can be overridden by the process starting the app
(see: How to capture the output from a console mode application).
Use the code below to find where the output is going:
}
program SampleConsoleApp;
**$APPTYPE CONSOLE}
uses
SysUtils, Windows, Dialogs;
var
hStdOut: THandle;
begin
writeln('Some output');
hStdOut := GetStdHandle(STD_OUTPUT_HANDLE);
if hStdOut = INVALID_HANDLE_VALUE then RaiseLastOsError;
case GetFileType(hStdOut) of
FILE_TYPE_UNKNOWN:
ShowMessage('Unknown output ');
FILE_TYPE_DISK:
ShowMessage('Output to a File');
FILE_TYPE_CHAR:
ShowMessage('Console output');
FILE_TYPE_PIPE:
ShowMessage('Pipe output');
end;
end.
**
When this app is run "normally"
it will display a 'Console output' message.
}
==============================
Console applications should be written to handle all exceptions - to prevent windows from displaying a dialog during its execution. "Simply" add all the code inside a try/except/finally blocks.
To return an exit code to the calling application or batch file from a Windows Console mode application written in Delphi call the Halt procedure, passing the exit code you wish to return.
There is a IsConsole variable defined in the Sytsem unit that indicates whether the module was compiled as a console application.
If you start a Console application form the command prompt or a batch file you have to use the start command to create a new console window for the application. Otherwise it takes over the DOS prompt window from which it was started.
Console applications also provide sophisticated low-level API support that gives direct access to a console's screen buffer and that enables applications to receive extended input information (such as mouse input). For example, the SetConsoleTextAttribute function sets the foreground (text) and background color attributes of characters written to the screen. Note that you need to add the Windows unit to the uses clause in order to use the API calls. The next peace of code could be used to display red text on a blue background:
------------------------
SetConsoleTextAttribute(GetStdHandle(
STD_OUTPUT_HANDLE),
FOREGROUND_RED OR
BACKGROUND_BLUE);
Writeln('Red text on a blue background !');
------------------------
Many of the nice things that conventional Pascal uses to manage the text screen, like clrscr, gotoxy and so on are not available in a console-based application. Again, Win API has all we need.
If you want to have console input/output for GUI applications you should use the AllocConsole and FreeConsole functions. The AllocConsole function allocates a new console for the calling process. The FreeConsole function detaches the calling process from its console. Here's the example:
--------------------------
var s: string;
begin
AllocConsole;
try
Write('Type something and press [ENTER]');
Readln(s);
ShowMessage('You typed ' + s);
finally
FreeConsole;
end;
end;
----------------------------
If you have ever tried to run a console application and capture the output, you may have tried to make sense of the topic in the Win32 help files called 'Creating a child process with redirected input and output'. The tip called "Capture the Output From a DOS Window" solves the problem. Here is another example of redirecting a console application output.
_I_Play_Chess
10-20-2003, 05:46 PM
This Chapter is the basics of DELPHI programing!
Take the time in reading it many times and work the examples.
leblitzer
10-22-2003, 07:27 AM
where is chap1
vBulletin v3.5.4, Copyright ©2000-2012, Jelsoft Enterprises Ltd.