Pascal Newsletter #1
INDEX
1. A FEW WORDS FROM THE EDITOR
2. INTRODUCTION TO BORLAND OBJECT PASCAL (III)
ABSTRACT CLASSES
INTERFACES
3. SUBCLASSING COMPONENTS
4. LINKS
________________________________________________________________________
1. A FEW WORDS FROM THE EDITOR
As we have announced in the latest issues of the Delphi Newsletter and
the Kylix Newsletter, this Pascal Newsletter now replaces these two
publications.
Little by little we are building our web site with the scarce resources
we have at hand. It still needs a lot of work, but you can visit it at:
http://www.latiumsoftware.com/en/index.php
There you will find the past issues of our newsletters and we already
have some mailing lists (for Delphi, Free Pascal and Visual Basic) and
we will soon open more. In the near future we expect to be able to offer
more contents: technical notes about programming, tips, tricks,
resources, components, articles of interest, information, links, etc.
To subscribe to any of this mailing lists please send a blank email to
the corresponding subscription address of the list:
Delphi <delphi-en-subscribe@yahoogroups.com>
Free Pascal <freepascal-en-subscribe@yahoogroups.com>
V. Basic <vbasic-en-subscribe@yahoogroups.com>
You will then have to reply to a confirmation message you should get a
few seconds after you subscribe, and then you will receive a welcome
message explaining how to send messages to the list and other stuff.
If you need more information, please check our web site or send us an
email.
Regards,
Ernesto De Spirito
eds2008 @ latiumsoftware.com
________________________________________________________________________
JfControls Library. Multi-language. Multi-appearance. Skins. Privileges.
More than 40 integrated and customizable components. Impressive GUI.
Centralized resources administration. Multiple programming problems
solved. For Delphi 3-2006 & C++ Builder 3-6. http://www.jfactivesoft.com
________________________________________________________________________
2. INTRODUCTION TO BORLAND OBJECT PASCAL (III)
This article is the continuation of a series of articles we started in
the Kylix Newsletter aimed at providing a quick introduction to Borland
Object Pascal for newcomers. We assume the reader has programming
experience in some other OOP language, like C++ or Java and already
understands basic concepts like encapsulation, inheritance and
polymorphism.
ABSTRACT CLASSES
Delphi allows the definition of abstract classes. What is an abstract
class? An abstract class is a class not meant to be instantiated, but to
be subclassed, so you cannot create objects of this class, but you can
derive it to create descendants, either abstract or not.
Let's recall that in the preceding article of this series we defined a
polymorphic class that had a virtual method overridden by its
descendants:
interface
type
TFigure = class(TObject)
procedure Show; virtual;
end;
TCircle = class(TFigure)
procedure Show; override;
end;
TSquare = class(TFigure)
procedure Show; override;
end;
TOval= class(TCircle)
procedure Show; override;
end;
implementation
procedure TFigure.Show; begin ShowMessage('Figure'); end;
procedure TCircle.Show; begin ShowMessage('Circle'); end;
procedure TSquare.Show; begin ShowMessage('Square'); end;
procedure TOval.Show; begin ShowMessage('Oval'); end;
A variable of type TFigure, for example Figure, can hold a reference not
only to a TFigure object, but also to TFigure descendants (like TCircle,
TSquare and TOval). Since the method Show is virtual (not static), then
when Figure.Show is called, the method of the corresponding class of
object that Figure is referencing at the moment of the call will be
executed.
var
Figure: TFigure;
Circle: TCircle;
Square: TSquare;
Oval: TOval;
begin
// First create the objects
Circle := TCircle.Create;
Square := TSquare.Create;
Oval := TOval.Create;
Figure := Circle; // Figure now references a TCircle object
Figure.Show; // TCircle.Show is called
Figure := Square; // Figure now references a TSquare object
Figure.Show; // TSquare.Show is called
Figure := Oval; // Figure now references a TOval object
Figure.Show; // TOval.Show is called
// Release the objects
Circle.Free; Square.Free; Oval.Free;
end;
Now, what does this have to do with abstract classes? Well, let's
suppose that the Show method was so specific to the descendants of
TFigure to be implemented in a so general class like TFigure. Delphi
allows the creation of a class leaving some of its (virtual or
dynamic) methods undefined using the abstract keyword. For example,
we could declare TFigure this way:
TFigure = class(TObject)
procedure Show; virtual; abstract;
end;
By declaring on method as abstract, then the class is abstract and it
cannot be instantiated, but can be derived:
TCircle = class(TFigure)
procedure Show; override;
end;
TSquare = class(TFigure)
procedure Show; override;
end;
TOval= class(TCircle)
procedure Show; override;
end;
implementation
procedure TCircle.Show; begin ShowMessage('Circle'); end;
procedure TSquare.Show; begin ShowMessage('Square'); end;
procedure TOval.Show; begin ShowMessage('Oval'); end;
Notice this time we didn't define TFigure.Show since it is an abstract
method. The example we showed above would work exactly the same. Just
please note that we can declare a variable of type TFigure
var Figure: TFigure;
but we can't create an object of this class:
Figure := TFigure.Create; // This would produce a compiler error
The benefit of abstract classes is that they allow to set base classes
for polymorphism without having to provide default implementations for
methods that are usually too specific to the descendants to be known
beforehand when defining the class. For example you can define an
abstract string stack:
TCustomStringStack = class
procedure clear; virtual; abstract;
procedure push(s: string); virtual; abstract;
function pop: string; virtual; abstract;
function top: string; virtual; abstract;
end;
Descendants of TCustomStringStack would have at least these methods and
they can implement the stack any way. For example you can have a stack
based upon an array (let's call it TArrayStringStack), or a linked list
(TListStringStack) or a disk file (TDiskStringStack), but independently
of the implementation (not know when defining the abstract base class)
you know a variable of type TCustomStringStack can handle the basic
operations of all these types of stack (TArrayStringStack,
TListStringStack or TDiskStringStack) and is assignment compatible with
them.
The VCL is full of abstract classes (they normally have the word
"Custom" in their names) and many properties which are objects are the
type of an abstract class.
INTERFACES
Interfaces are a bit like classes, except:
* They are declared with the keyword 'interface' instead of 'class'.
* Their names usually start with 'I' rather than 'T'.
* They don't have constructors or destructors.
* They cannot be instantiated.
* They cannot have fields: only methods and properties.
* Since they have no fields, property read and write specifiers must be
methods
* Methods cannot be declared as virtual, dynamic, abstract, or override.
One could say that all methods are abstract.
* The default calling convention of the methods is register. Use stdcall
for interfaces shared among modules (especially if they are written in
different languages). Use safecall to implement methods of dual
interfaces and CORBA interfaces.
* Visibility specifiers (public, private, protected and published) are
not allowed. All members are public.
* They are descendants of IUnknown (the equivalent of TObject).
* They can have a GUID (globally unique identifier) to uniquely identify
an interface. It is used when querying the interface to get references
to its implementations. A GUID is a 16 bytes value (see the example).
This is an interface declaration:
type
IRotation = interface(IUnknown)
['{01234567-89AB-CDEF-0123-456789ABCDEF}']
procedure Rotate(degrees: single); stdcall;
end;
The purpose of an interface is eventually to be implemented by a class.
It can also serve as a base interface for its descendants. A class can
implement many interfaces. For example, our TFigure class can implement
the IRotation interface we've just defined:
type
TFigure = class(TObject, IRotation)
procedure Show; virtual, abstract override;
procedure Rotate(degrees: single); stdcall;
function QueryInterface(const IID: TGUID; out Obj):
HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
Where did all these methods came from? Well, a class that implements
interfaces should declare and implement all their methods. In this case,
all methods of IRotation should be implemented. IRotation has 4 methods:
Rotate and three methods that inherit from IUnknown. If we don't want to
implement these three methods we can derive from TInterfacedObject (a
direct descendant of TObject) or TComponent (a common ancestor of a lot
of visual an nonvisual components) since they already implement these
three methods, so to avoid having to do this ourselves, the easiest way
to implement an interface is using a derived class of one of this two
classes or any of their descendants. For example:
TFigure = class(TInterfacedObject, IRotation)
procedure Show; virtual; abstract;
procedure Rotate(degrees: single); stdcall;
end;
This way we only have to worry about Rotate. An implemented method can
be virtual and abstract, so we can leave the implementation to
descendants of TFigure if we want:
interface
type
TFigure = class(TInterfacedObject, IRotation)
procedure Show; virtual; abstract;
procedure Rotate(degrees: single); virtual; stdcall; abstract;
end;
TSquare = class(TFigure)
procedure Show; override;
procedure Rotate(degrees: single); override; stdcall;
end;
TOval = class(TSquare)
procedure Show; override;
procedure Rotate(degrees: single); override; stdcall;
end;
implementation
procedure TSquare.Show; begin ShowMessage('Square'); end;
procedure TSquare.Rotate(degrees: single);
begin ShowMessage('Square rotated'); end;
procedure TOval.Show; begin ShowMessage('Oval'); end;
procedure TOval.Rotate(degrees: single);
begin ShowMessage('Oval rotated'); end;
Now we can write a code like this:
var
Square: TSquare;
Oval: TOval;
Rotation: IRotation;
begin
Square := TSquare.Create;
Oval := TOval.Create;
Rotation := Square;
Rotation.Rotate(45); // Calls TSquare.Rotate
Rotation := Oval;
Rotation.Rotate(90); // Calls TOval.Rotate
end;
As you can see, the use of interfaces is very similar to polymorphism.
The difference is that it is more open since an interface variable can
be assigned an object of ANY class as long as the class implements the
interface, not as long as it is derived from certain base class. For
example, if we had another class not descendant of TFigure that
implementes IRotation, like this one:
TMyButton = class(TComponent, IRotation)
procedure Rotate(degrees: single); stdcall;
end;
Then these sentences would also be valid:
MyButton := TMyButton.Create;
Rotation := MyButton;
Rotation.Rotate(180); // Calls TMyButton.Rotate
There are a couple of important things to know about interfaces and the
implementation of the 3 methods of IUnknown made by TInterfacedObject.
First, the objects are referenced counted, meaning that when you make an
assignment, the value of the count is incremented (_AddRef is implicitly
called). This value is stored in TInterfacedObject.FRefCount and after
you create an object it is set to 0. For example, after
Square := TSquare.Create;
Square.FRefCount will be 0. After an assignment like
Rotation := Square;
Square.FRefCount will be 1. When you assign another object to Rotation,
like
Rotation := Oval;
the count is decremented (_Release is implicitly called), and if it
reaches 0, then the object is freed. This explains why we didn't free
Square in the above example. We didn't free Oval because upon exit of a
procedure or function, it's like if local interface variables were
assigned nil, so the count is decremented reaching 0, so the object is
freed.
Sometimes this behavior can really be a bother. What if we needed the
object after assigning the interface variable to another object? Well,
we have two choices: tweaking the value of FRefCount or defining a new
object like TInterfacedObject but implementing IUnknown methods in a
different way.
For the first option, the example procedure we used above would be:
var
Square: TSquare;
Oval: TOval;
Rotation: IRotation;
begin
Square := TSquare.Create;
Oval := TOval.Create;
InterlockedIncrement(Square.FRefCount);
Rotation := Square;
Rotation.Rotate(45); // Calls TSquare.Rotate
InterlockedIncrement(Oval.FRefCount);
Rotation := Oval;
InterlockedDecrement(Square.FRefCount);
Rotation.Rotate(90); // Calls TOval.Rotate
Rotation := nil;
InterlockedDecrement(Oval.FRefCount);
Square.Free;
Oval.Free;
end;
For the second option, we could define a TInterfacedObj class:
interface
type
TInterfacedObj = class(TObject, IUnknown)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult;
stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
implementation
function TInterfacedObj.QueryInterface(const IID: TGUID; out Obj):
HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := HResult($80004002); // E_NOINTERFACE
end;
function TInterfacedObj._AddRef: Integer; begin Result := 1; end;
function TInterfacedObj._Release: Integer; begin Result := 1; end;
TFigure should then be derived from our TInterfacedObj:
TFigure = class(TInterfacedObj, IRotation)
...
And our test procedure could be like this one:
var
Square: TSquare;
Oval: TOval;
Rotation: IRotation;
begin
Square := TSquare.Create;
Oval := TOval.Create;
Rotation := Square;
Rotation.Rotate(45); // Calls TSquare.Rotate
Rotation := Oval;
Rotation.Rotate(90); // Calls TOval.Rotate
Square.Free;
Oval.Free;
end;
This looks like a more "natural" way of programming.
________________________________________________________________________
3. SUBCLASSING COMPONENTS
In the last issue of the Delphi Newsletter we were developing a simple
file search application. In the example, we placed the files matching
the search criteria in a TListView component. If the user double-clicks
a file name, we open the document with the associated, and if the user
double-clicks the folder path, then we opened the directory with Windows
Explorer. We saw that the TListView component doesn't have properties
and methods to know directly which column was clicked, so we had to run
in a bit of a trouble to determine that. Perhaps it would be a good idea
to make a new TListView capable of giving us that kind of information,
so we can also use it in other programs.
Of course, we are not going to make that new component out of nothing,
but rather we are going to subclass or derive a new component from
TListView, so our work would be minimum.
In the "Components" menu, choose the "New Component..." item. In the
New Component dialog choose TListView as the ancestor type, write
TListViewX as the name for our new class, give a path for the unit in
the Unit file name text box and click OK. Now we have a new unit. We
could have done it by hand since it's no rocket science as you can see.
Well, now all we have to do is create an enhanced version of GetItemAt
that gives us more information. There are many ways to do this, but what
we are going to do is add a third parameter passed by reference to a new
procedure that we are going to name GetItemAtX.
type
TListViewX = class(TListView)
public
function GetItemAtX(X, Y: integer; var Col: integer): TListItem;
end;
Now we should implement that procedure:
implementation
function TListViewX.GetItemAtX(X, Y: integer;
var Col: integer): TListItem;
var
i, n, RelativeX, ColStartX: Integer;
ListItem: TlistItem;
begin
Result := GetItemAt(X, Y);
if Result <> nil then begin
Col := 0; // First column
end else if (ViewStyle = vsReport) and (TopItem <> nil) then begin
// First, let's try to find the row
ListItem := GetItemAt(TopItem.Position.X, Y);
if ListItem <> nil then begin
// Now let's try to find the Column
RelativeX := X - ListItem.Position.X - BorderWidth;
ColStartX := Columns[0].Width;
n := Columns.Count - 1;
for i := 1 to n do begin
if RelativeX < ColStartX then break;
if RelativeX <= ColStartX
+ StringWidth(ListItem.SubItems[i-1]) then begin
Result := ListItem; Col := i;
break;
end; // if
Inc(ColStartX, Columns[i].Width);
end; // for
end; // if
end; // if
end;
It is not necessary that you understand this code to understand how to
subclass a component. The important thing is that we declared and
implemented a new method.
Now it is time to add our new component to the components palette. In
the register procedure notice that our new component will be added to
the samples palette. You can change it if you want. To install the
component go to the "Components" menu and choose "Install Component...".
A dialog box should appear. Just click OK if you want this new control
to be part of the standard user's package. Another dialog should appear
showing us the package. Click "Compile". If this is the first time you
compile a package, a message should appear querying whether you want to
continue. Click "Yes". If there were no errors, the package was built
and the component added to the palette. You can close the package
window (save the changes when prompted).
Now, how do we change the old TListView for our new TListViewX in our
file finder application? Right click on the form and choose View as Text
from the context menu. You will see a text representation of your form.
Find TListView and replace it for TListViewX. Right click on the code
and choose View as Form to go back to the visual representation. The
keyboard shortcut for switching between these two representations is
[Alt+F12]. In the unit code you should also replace TListView for
TListViewX.
Now that we have a new method we can use, our DblClick event handler
should be far more easier to understand:
procedure TForm1.ListView1DblClick(Sender: TObject);
var
Col: Integer;
ListItem: TListItem;
begin
ListItem := ListView1.GetItemAtX(Last.X, Last.Y, Col);
if ListItem <> nil then begin
if Col = 0 then begin
if ShellExecute(Self.Handle, nil,
PChar(ListItem.SubItems.Strings[0] + ListItem.Caption),
nil, nil, SW_SHOWMAXIMIZED) <= 32 then begin
Application.MessageBox('Couldn''t execute the application',
'Error', MB_ICONEXCLAMATION);
end; // if
end else if Col = 1 then begin
if ShellExecute(Self.Handle, 'explore',
PChar(ListItem.SubItems.Strings[0]),
nil, nil, SW_SHOWMAXIMIZED) <= 32 then begin
Application.MessageBox('Couldn''t execute the application',
'Error', MB_ICONEXCLAMATION);
end; // if
end; // if
end; // if
end;
You can try the application to see it working. You can download the full
source code from our web site:
http://www.latiumsoftware.com/en/file.php?id=p01
________________________________________________________________________
4. LINKS
Sebastian sent us this link where you can see some screenshots of Delphi
for Linux (part of the Kylix project):
* Dr. Bob
http://www.drbob42.com/Kylix/hotshots.htm
Do you have questions? Visit this site:
* UDDF - Unofficial Delphi Developers FAQ
http://www.ellipse-data.com/delphifaq/devfaq/indexnf.html
________________________________________________________________________
YOU CAN HELP US
We need your help to keep this newsletter going and growing. You can
help by referring the newsletter to your colleagues:
http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php
Or you can help by voting for us in some or all of these rankings to
give more visibility to our web site and thus increase the number of
subscriptions to this newsletter:
http://www.programmingpages.com/?r=latiumsoftwarecomenpascal
http://top100borland.com/in.php?who=20
It's just a few seconds for you that REALLY mean a lot to us.
________________________________________________________________________
If you haven't received the full source code examples for this issue,
you can get them from http://www.latiumsoftware.com/en/file.php?id=p01
________________________________________________________________________
This newsletter is provided "AS IS" without warranty of any kind. Its
use implies the acceptance of our licensing terms and disclaimer of
warranty you can read at http://www.latiumsoftware.com/en/legal.php
where you will also find a note about legal trademarks. Articles are
copyright of their respective authors and they are reproduced here with
their permission. You can redistribute this newsletter as long as you do
it in full (including copyright notices), without changes, and gratis.
________________________________________________________________________
Main page: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php
Group home page: http://groups.yahoo.com/group/pascal-newsletter/
Subscribe/join: pascal-newsletter-subscribe@yahoogroups.com
Unsubscribe/leave: pascal-newsletter-unsubscribe@yahoogroups.com
Problems with your subscription? eds2008 @ latiumsoftware.com
________________________________________________________________________
Latium Software http://www.latiumsoftware.com/en/index.php
Copyright (c) 2000 by Ernesto De Spirito. All rights reserved.
________________________________________________________________________
|