Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / Delphi

Inherit Me This... Inherit Me That....

Rate me:
Please Sign up or sign in to vote.
4.88/5 (10 votes)
26 Nov 2011CPOL4 min read 21.5K   137   3   10
This article explains how inheritance in Delphi helps you to design and create your classes using a nice Object Orientated approach

Introduction

This article is my humble attempt at explaining how Inheritance and Polymorphism can be used to help you use an object orientated guideline for creating classes and creating descendants of these classes and a cool trick for creating the correct instance of the descendant class you want to create.

Background

Whether you know it or not, we use inheritance every time we use Delphi. When you create a new form, you are in fact using inheritance.

Delphi
TForm1 = class(TForm)
end;  

TForm1 is inheriting from the base class TForm. This is how your new form knows exactly how to be a form (like how it must be drawn, what events it has, what it can do, etc.) without you needing to do anything extra. But how can you use this to make your applications more robust and leverage this kind of practice? Well, this is what I am hopefully going to help demystify.

What the Code Does

All the demonstration application does is draw two types of shapes in 4 different colours. I start by making a (base) class, TShape, and create two descendant classes TEllipse and TRectangle. The aim of TShape is to draw a shape onto a TPaintBox component. When the TShape class is defined, we know the following information:

  • What color I need to draw with
  • The co-ordinates where that needs to be drawn

So the class definition looks like this:

Delphi
TShape = class(TObject)
private
  FTop : Integer;
  FBottom : Integer;
  FRight : Integer;
  FLeft : Integer;
  FBrushColor : TColor;

  procedure SetTopLeft(const Value : TPoint);
  function GetBottomRight : TPoint;
  function GetTopLeft : TPoint;
  procedure SetBottomRight(const Value : TPoint);
public
  constructor Create(
    const ABrushColor : TColor;
    const ATop : Integer;
    const ALeft : Integer); overload;
  constructor Create(
    const ABrushColor : TColor;
    const ATop : Integer;
    const ALeft : Integer;
    const ARight : Integer;
    const ABottom : Integer); overload;
  constructor Create(
    const ABrushColor : TColor;
    ATopLeft : TPoint); overload;
  constructor Create(
    const ABrushColor : TColor;
    ATopLeft : TPoint;
    ABottomRight : TPoint); overload;

  procedure Draw(
    ACanvas : TCanvas); virtual; abstract;

  property Top : Integer read FTop write FTop;
  property Left : Integer read FLeft write FLeft;
  property Right : Integer read FRight write FRight;
  property Bottom : Integer read FBottom write FBottom;
  property TopLeft : TPoint read GetTopLeft write SetTopLeft;
  property BottomRight : TPoint read GetBottomRight write SetBottomRight;
  property BrushColor : TColor read FBrushColor;
end;

The only thing the TShape object will not know is actually HOW to draw itself. This is where Polymorphism comes in. In the TEllipse and TRectangle, you will see an overridden method Draw. This is how the TShape will know how to actually do what it needs to do. And the definition for them looks like this:

Delphi
  TEllipse = class(TShape)
  public
    procedure Draw(
      ACanvas : TCanvas); override;
  end;

// Actual implementation...
procedure TEllipse.Draw(
  ACanvas: TCanvas);
begin
  ACanvas.Brush.Color := BrushColor;
  ACanvas.Ellipse(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y);
end;

  TRectangle = class(TShape)
  public
    procedure Draw(
      ACanvas : TCanvas); override;
  end;

// Actual implementation...
procedure TRectangle.Draw(
  ACanvas: TCanvas);
var
  LRect : TRect;
begin
  ACanvas.Brush.Color := BrushColor;
  LRect.TopLeft := TopLeft;
  LRect.BottomRight := BottomRight;
  ACanvas.Rectangle(LRect);
end;

And how that Draw method is implemented will determine how the shape is actually drawn. If you decide to build your own descendant from the TShape object, you will know what I mean :).

PLEASE NOTE: The code in the source files has a lot of commenting in it to help explain what is happening. Please read the code to get a better indication of why I did things the way I did...

Now, in the main form, I need to ensure a few things:

  • What colour I must use
  • What shape I must use
  • What are my co-ordinates so I know where to draw
  • How many objects do I actually need to draw

This is why I use specific events and variables on my main form:

  • 2 variables to know where the points of my shape need to be
  • The mouse up and down events (on the paint box) to ensure that I can set the points of the shape correctly
  • An object which will hold all the TShape objects I need
  • Ensure that any TShape objects that are created are destroyed correctly

As such, my main form looks something like this:

Delphi
TMainFrm = class(TForm)
  procedure PaintBoxMouseDown(
    Sender : TObject;
    Button : TMouseButton;
    Shift : TShiftState;
    X : Integer;
    Y : Integer);
  procedure PaintBoxMouseUp(
    Sender : TObject;
    Button : TMouseButton;
    Shift : TShiftState;
    X : Integer;
    Y : Integer);
  procedure PaintBoxPaint(
    Sender : TObject);
  procedure FormCreate(
    Sender : TObject);
  procedure FormDestroy(
    Sender : TObject);
private
  FTopLeft : TPoint;
  FBottomRight : TPoint;
  FShapes : TObjectList;
end;

With the implementation looking like this:

Delphi
procedure TMainFrm.PaintBoxMouseDown(
  Sender : TObject;
  Button : TMouseButton;
  Shift : TShiftState;
  X : Integer;
  Y : Integer);
begin
  FTopLeft.X := X;
  FTopLeft.Y := Y;
end;

procedure TMainFrm.PaintBoxMouseUp(
  Sender : TObject;
  Button : TMouseButton;
  Shift : TShiftState;
  X : Integer;
  Y : Integer);
var
  LColor : TColor;
  LShapeRef : Ref_Shape;
begin
  case ComboBox1.ItemIndex of
    0 : LShapeRef := TEllipse;
    1 : LShapeRef := TRectangle;
  else
    LShapeRef := TEllipse;
  end;
  case ComboBox2.ItemIndex of
    0 : LColor := clRed;
    1 : LColor := clGreen;
    2 : LColor := clBlue;
    3 : LColor := clBlack;
  else
    LColor := clRed;
  end;
  FBottomRight.X := X;
  FBottomRight.Y := Y;
  FShapes.Add(LShapeRef.Create(LColor, FTopLeft, FBottomRight));
  PaintBox.Repaint;
end;

procedure TMainFrm.PaintBoxPaint(
  Sender : TObject);
var
  LShape : TShape;
  i : Integer;
  LCanvas : TCanvas;
begin
  LCanvas := TPaintBox(Sender).Canvas;
  LCanvas.Brush.Color := clWhite;
  LCanvas.FillRect(TPaintBox(Sender).Canvas.ClipRect);
  for i := 0 to FShapes.Count - 1 do
  begin
    LShape := TShape(FShapes.Items[i]);
    LShape.Draw(LCanvas);
  end;
end;

procedure TMainFrm.FormCreate(
  Sender : TObject);
begin
  FShapes := TObjectList.Create(True);
end;

procedure TMainFrm.FormDestroy(
  Sender : TObject);
begin
  FreeAndNil(FShapes);
end;

Once again, for simplicity's sake, I left out the comments. To see how this all ties in, all you need to do is run the accompanying application in the download and you are good to go :).

Points of Interest

Inadvertently, you have now been introduced to the Template Design Pattern. It leverages the fact that the base class(es) you define do certain common things for you that your descendant classes use so that you don't need to re-code them. This enables you to have cleaner, more maintainable code (because instead of the common code being rewritten in several places, it is now only in one place). It also allows you to design your descendant classes to focus on what they were actually created for without worrying about the common methods/actions/properties because that has already been catered for in the ancestor class.

You will also see in my code that I have a really strange declaration:

Delphi
Ref_Shape = class of TShape;

This is called a class reference declaration and what that means is that you can create an instance of a TShape descendant using a variable of type Ref_Shape. Which is why you will see the code the way you do in the PantBoxMouseUp event. Cool trick hey? :).

For a more detailed example of how to use the topics I raised in this article, please take a gander at The Configurator[^].

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
South Africa South Africa
I've been coding for the better part now of about 15 years, starting in the good old Turbo Pascal 7 days, when I made my very first computer game ( my own version of Reversi / Othello) and I was hooked, through to Delphi 2009 (and higher, just didn't use it that much).

For the last 3 years or so I have been off and on using C#/ASP.NET (and even more recently Entity Framework and MVC) and I think it is TOTALLY awesome. Having said that though, I must admit that I will always be a Delphi guy at heart. I've dabbled in a few other languages and am always on the lookout to keep in touch with what's new in the software development scene.

When I am not coding, I keep myself busy writing short stories[^], partying hard, singing at karaoke (don't judge me!), trying to learn how to play the guitar and generally helping others wherever I can.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 113956215-Nov-15 10:46
Member 113956215-Nov-15 10:46 
GeneralRe: My vote of 5 Pin
nortee5-Nov-15 10:49
nortee5-Nov-15 10:49 
QuestionPretty... Pin
StM0n26-Nov-11 4:39
StM0n26-Nov-11 4:39 
AnswerRe: Pretty... Pin
nortee26-Nov-11 4:43
nortee26-Nov-11 4:43 
GeneralMy vote of 1 Pin
snortle26-Nov-11 1:09
snortle26-Nov-11 1:09 
GeneralRe: My vote of 1 Pin
nortee26-Nov-11 1:39
nortee26-Nov-11 1:39 
GeneralRe: My vote of 1 Pin
#realJSOP27-Nov-11 1:34
mve#realJSOP27-Nov-11 1:34 
GeneralRe: My vote of 1 Pin
nortee27-Nov-11 3:03
nortee27-Nov-11 3:03 
GeneralRe: My vote of 1 Pin
Stefan_Lang28-Nov-11 5:22
Stefan_Lang28-Nov-11 5:22 
GeneralRe: My vote of 1 Pin
smags1321-Dec-11 11:25
smags1321-Dec-11 11:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.