Delphi Component Writers' FAQ

Archive-name: component-writers.txt
Posting-Frequency: biweekly
Last-modified: July 06, 1997

The Delphi Component Writing FAQ

Edited By:      John M. Miano   mi...@worldnet.att.net
Version:        8
Last Updated:   06-Jun-1997

Changes:

Several corrections submitted by readers have been included.

The original version of this document is now at

http://home.att.net/~miano

------------------------------------------------------------------------
Table of Contents

Section 1 - Introduction
1.1. What is the purpose of this document?

Section 2 - IDE
2.1. How can I locate problems that result when my component is used
     in the IDE?
2.2. How do I view assembly langugage the Delphi Generates?
2.3. I can create my control at run time but it crashes at design
     time.  What is wrong?
2.4. How can I create a component that cannot be dropped on a form?
2.5. What is an easy way to backtrack through source code?
2.6. How can I add to the popup menu that is displayed when the right
     mouse button is clicked on my component?
2.7. How come I get I/O 103 in design mode?
2.8. Why are my property values not getting saved when I use a
     component editor?

Section 3 - Using other components within a component
3.1. How can I add a scroll bar component to my component and have it
     work at in design mode?
3.2. How do I create a Windows '95-style scroll bar?

Section 4 - Bound Controls
4.1. Where is the documentation for the TDataLink class?
4.2. How can I tell the number of the record I am on in a data set?

Section 5 - VCL
5.1. How can I step through the VCL source while debugging?
5.2. My component references other components.  How can I tell if a
     component my component references had been deleted?
5.3. What are component messages?
5.4. My control has focus but it is not getting keystroke messages.
     What's happening?

Section 6 - Other Sources of Information
6.1. Are there any books one how to write Delphi components?
6.2. Are there any good web sites with information on how to write
     components?

Section 7 - Persistent Objects
7.1. How can I save a complex object containing child objects to the
     .DFM file.
7.2. How can I tell if my constructor is being called for an object
     being loaded from a stream?
7.3. How can I tell if my component's properties are being saved
     correctly to the form file?

Section 8. Tools for Delphi
8.1. Is there YACC and LEX for Delphi?
8.2. How can I write code to display JPEG files?

Section 9. Basic Programming Techniques
9.1. How do I create a dynamic array of objects?
9.2. Where is Wincrt in Delphi 2.0?
9.3. What should the base class be for my component?

Section 10. Advanced Programming Techniques
10.1. Is there a Delphi equivalent to C++'s I/O stream classes?
10.2. How can I get the text equivalent for an enumerated type?

Section 11. Component Virtual Methods
11.1. How can I find out when my component has had its window handle
      created?
11.2. How can I tell when all the components on my form have been
      loaded?
11.3. Where is the best place to draw my control?

Section 12. Windows API Functions
12.1. I am trying to scroll the contents of my control but I get an
      ugly flicker effect.  How can I eliminate this?
12.2. How can I restart windows?
12.3. How can I batch updates to when changing the appearance of my
      control?

Section 13. Control Borders
13.1. How come my control does not have a 3D border even when I have
      CTL3D set to True?
13.2 How do I implement a BorderStyle property?

Section 14. Control Styles
14.1. How do I stop my control from flickering when it gets repainted?

Section 15. Windows Messages
15.1. How come my control does not get keystroke messages for the
      arrow keys?
15.2. Is there an equivalent of Visual Basic's "DoEvents" statement?

------------------------------------------------------------------------
Section 1 - Introductions

1.1. What is the purpose of this document?

The purpose of this document is to answer common or undocumented
questions related to writing Delphi components.  After spending a
ridiculous amount of time trying to decipher the TDataLink component
it occured to me that there ought to be some way of recording and
sharing solutions to the problems encountered while writing
controls. All information is provided as is.  There are no guarantee
as to its correctness.

If you have any questions you would like to be answered or have any
contributions you think would be suitable for inclusion please send
them to the editor.  Reports of errors or omissions are also welcome.

In addition to including more answers and questions I am trying to
expand in two more areas:

1. Tools that are of interest to advanced developers: These might not
be directly related to component writing but they will have some
integration with Delphi.

2. References to publications: Since there is not enough room in an
FAQ for extensive examples as I come across examples of how to do
things in print I will include them here.  This will not be an
academic style bibliography I only indend to include references to the
best or most accessible document or publication.

If you have an suggestions for any of these they are also welcome.
Keep in mind that I cannot include an item if I cannot verify it.

------------------------------------------------------------------------
Section 2 - IDE Problems

2.1. How can I locate problems that result when my component is used
     in the IDE?

The only solution to locating problems I have found is to:

1. In Delphi go to Tools/Options then go to the "Library" page.
Check the "Compile With Debug Info" box.
2. Rebuild the library.
3. Run Delphi from within Turbo De{*word*81}.
4. Use "File/Change Dir" to include the source directories.

If you get a GPF you can use view the stack and get some idea where
the problem is occuring.

2.2. How do I view assembly langugage the Delphi Generates?

From Glen Boyd

Run the REGEDIT program and go to
"HKEY_CURRENT_USER\Software\Borland\Delphi\2.0\Debugging" and add a
string value called EnableCPU and set its string value to 1.  This
adds the CPU window to the view menu.  The CPU window is active at run
time for stepping through and stuff like that.

2.3. I can create my control at run time but it crashes at design
     time.  What is wrong?

1. Your component must descend from TComponent

2. Your constructory and destructor declarations must look like:

Constructor Create (AOwner : TComponent) ; Override ;
Destructor Destroy ; Override ;

3 All fields that are published must be either ordinal, single,
double, extended, comp, currency, string, small set, method pointer or
a class.  The Delphi compiler does not produce an error if you use any
other type in a published section. However you will get an GPF if you
try to use a control that contains a published declaration of any
other type.

The following declarations will cause serious problems if you attempt
to use TMyComponent in design mode.

Type
  TComplex = Record
        RealPart : Double ;
        ComplexPart : Double ;
        End ;

class TMyComponent = Class (TComponent)
  Private
    F1 : TComplex ;
  Published
    Property P1 : TComplex Read F1 Write F1 ;
    End ;

2.4. How can I create a component that cannot be dropped on a form?

From Ray Lischner

If you don't want the user to be able to drop the component on a form
then use then RegisterNoIcon and RegisterClass procedures.

2.5. What is an easy way to backtrack through source code?

From Ray Konopka

When viewing the source for the VCL units, it's definitely a good idea
to become comfortable with bookmarks in the editor.  That is,
Ctrl+Shift+N, when N is a number 0-9, to set a bookmark.  Jump to a
bookmark using Ctrl+N.

2.6. How can I add to the popup menu that is displayed when the right
     mouse button is clicked on my component?

You do this by creating a Component Editor.  One would think that a
component editor would be something that is called from the popup menu
but it actually controls what appears in the menu or rather the items
that your component can add to the menu.

The steps to follow are:
1. Create a class that derives from TComponentEditor
2. In your class override the method GetVerbCount, GetVerb, and
   ExecuteVerb.
3. In your component's Register procedure add a call to
   RegisterComponentEditor that associates your component editor with
   your component.

This topic is described well in the book "Developing Delphi
Components".

2.7. How come I get I/O 103 in design mode?

You probably have WriteLn statements in your code.

2.8. Why are my property values not getting saved when I use a
     component editor?

I have found that when using a component editor that a component's
properties will not be saved. The property appears to be correct in
design mode but when you run or save the property is set to a
different value.

It appears that a property editor needs to call

 Designer.Modified

to let Delphi know that you have changed a property value.

------------------------------------------------------------------------
Section 3 - Using other components within a component

3.1. How can I add a scroll bar component to my component and have it
     work at in design mode?

You need to define your own scroll bar class that intercepts the
CM_DESIGNHITTEST message.

TMyScrollBar = class (TScrollBar)
      Procedure CMDesignHitTest (var Message : TCMDesignHitTest) ;
        Message CM_DESIGNHITTEST ;
    End ;

Procedure TMyScrollBar.CMDesignHitTest (var Message :
  TCMDesignHitTest) ;
  Begin
  Message.Result := 1 ;
  End ;

When your component creates one of these scroll bars it needs to use

TMyScrollBar.Create (Nil)

rather than

TMyScrollBar.Create (Self)

otherwise the scroll bar will display sizing handles when it is click.
This means you need to be sure to explicitly free the scroll bar in
your component's destructor.

3.2 How do I create a Windows '95-style scroll bar?

You need to set the page size for the scroll bar.  The following code
sequence illustrates this:

Procedure SetPageSize (ScrollBar : TScrollBar ; PageSize : Integer) ;
  Var
    ScrollInfo : TScrollInfo ;
  Begin
  ScrollInfo.cbSize := Sizeof (ScrollInfo) ;
  ScrollInfo.fMask := SIF_PAGE ;
  ScrollInfo.nPage := PageSize ;  
  SetScrollInfo (ScrollBar.Handle, SB_CTL, ScrollInfo, True) ;
  End ;

To retrieve the page size use:

Function GetpageSize (ScrollBar : TScrollBar) ;
  Var
    ScrollInfo : TScrollInfo ;
  Begin
  If HandleAllocated Then
    Begin
    ScrollInfo.cbSize := Sizeof (ScrollInfo) ;
    ScrollInfo.fMask := SIF_PAGE ;
    GetScrollInfo (ScrollBar.Handle, SB_CTL, ScrollInfo) ;
    Result := ScrollInfo.nPage ;
    End ;

------------------------------------------------------------------------
Section 4 - Bound Controls

4.1. Where is the documentation for the TDataLink class?

The C++Builder documentation contains a description of the TDataLink
class. It is not in any Delphi documentation so far. For those of you
who do not have C++Builder here is the description that has been in
this document for a long time:

Properties:
===========

Property:       Active : Boolean (Read Only)
----------------------------------------

Returns true when the data link is connected to an active datasource.
The ActiveChanged method is called to give notification when the state
changes.

Property:       ActiveRecord: (Read/Write)
--------------------------------------

This sets or returns the current record within the TDatalink's buffer
window.  Valid values are 0..BufferCount - 1.  There appear to be no
range checks so assigning values outside this range produces
unpredictable results.

Property:       BufferCount: (Read/Write)
-------------------------------------

The TDataLink maintains a window of records into the dataset This
property is the size of this window and determines the maximum number
of row that can be view simultaneously.  For most controls you would
use a BufferCount of one.  For controls such as a data grid this value
is the number of visible rows.

Property:       DataSet: TDataSet (Read)
------------------------------------

The dataset the TDataLink is attached to.  This is a shortcut to
DataSource.DataSet.

Property:       DataSource: TDataSource (Read/Write)
------------------------------------------------

Sets or returns data source control the TDataLink is attached to.

Property:       DataSourceFixed: Boolean (Read/WRite)
-------------------------------------------------

This property is used to prevent the data source for the TDataLink
from being changed.  If this property is set to Trye then assigning a
value to the DataSource property will result in an exception.

Property:       Editing: Boolean (Read Only)
----------------------------------------

Returns true if the datalink is in edit mode.

Property:       ReadOnly: Boolean (read/Write)
------------------------------------------

This property determines if the TDataLink is read only.  It does not
appear to affect the attached datalink or dataset.  If this property
is set to True the datalink will not go into edit mode.

Property:       RecordCount: Integer (Read)
---------------------------------------

The property returns the approximate number of records in the attached
dataset.

Methods:
========

function Edit: Boolean;
-----------------------

Puts the TDatalink's attached dataset into edit mode.

Return Value:
        True => Success
        False => Failure

procedure UpdateRecord;
-----------------------

It appears that this is a function that is intended to be called by
other parts of the data base interface and should not be called
directly.  All it does is set a flag and call UpdateData (described
below).

Virtual Methods
===============

The mechanism for having the TDataLink object communicate with a
component is to override these procedures.

procedure ActiveChanged
------------------------
This procedure is called whenever the datasource the TDataLink is
attached to becomes active or inactive.  Use the Active property to
determine whether or not the link is active.

procedure CheckBrowseMode
-------------------------
This method appears to get called before any changes take place to the
database.

procedure DataSetChanged;
-------------------------
This procedure
gets called when the following events occur:

   o Moving to the start of the dataset
   o Moving to the end of the dataset
   o Inserting or Appending to the dataset
   o Deleting a record from the dataset
   o Canceling the editing of a record
   o Updating a record

The non-overridden action for this procedure is to call

        RecordChanged (Nil)

procedure DataSetScrolled(Distance: Integer)
--------------------------------------------
This procedure is called whenever the current record in the dataset
changes.  The Distance parameter tells how far the buffer window into
the dataset was scrolled (This seems to always be in the range -1, 0,
1).

Use the ActiveRecord to determine which record within the buffer
window is the current one.

It is not possible to force a scroll of the buffer window.

procedure FocusControl(Field: TFieldRef)
----------------------------------------
This appears to get called as a result of Field.FocusControl.

procedure EditingChanged
-------------------------
This procedure is called when the editing state of the TDataLink
changes.  Use the Editing property to determine if the TDataLink is in
edit mode or not.

procedure LayoutChanged
-----------------------
This procedure is called when the layout of the attached dataset
changes (e.g. column added).

procedure RecordChanged(Field: TField)
--------------------------------------
This procedure gets called when:

   o The current record is edited
   o The record's text has changed

If the Field parameter is non-nil then the change occured to the
specified field.

procedure UpdateData
--------------------
This procedure is called immediately before a record is updated in the
database.  You can call the Abort procedure to prevent the record from
being updated.

4.2. How can I tell the number of the record I am on in a data set?

At first glance the RecNo property for a data set appears to be what
you want but unfortunately this only works with dBase and Paradox
tables.

The way you can keep track of the current record number is to create a
class that derives from TDataLink.  The main things you need to do
are:

o Override the DataSetScrolled method to keep track of the number of
  times the current record has been moved forwards or backwards.
o Override the DataSetChanged method to determine when a jump the
  start or end of the dataset has occured.

You can attach an object or your class to a data source and use it to
keep track of the current record.

------------------------------------------------------------------------
Section 5 - VCL
5.1. How can I step through the VCL source while debugging?

Copy the VCL source modules you are interested in stepping through to
your project directory then rebuild the VCL library.  You will then be
able to step through the VCL source modules.

5.2. My component references other components.  How can I tell if a
     component my component references has been deleted?

From Max Nilson                                  

A little documented part of TComponent and its decendants is the the
Notification method. This method is primarily used to detect cases
where components that you are referencing are being deleted. It does
have other features but I have not yet thought of a reason to use
them. For Borlands explanation of these things search for Notification
and FreeNotification in the VCL help. Don't bother looking in the
Component Writer's Guide 8-)

When ever you reference another component from your component, for
example including a TDataSource property in your component, you should
override the Noficication method and respond to it by checking that
the component you are referencing is not being deleted. You should
also use the FreeNotification method to ensure that you are notified
even it the TComponent you are referencing is in another module. By
default you only recieve notifications of componets in the same module
as your component has been placed, and now that Borland has provided
data modules you are sure to run this case more often than you expect.

If you don't use the Nofification method you will find (as I did) that
deleting a component that you reference will place the Delphi IDE into
an extremely unstable state. It doesn't quite crash, but its very hard
to do anything afterwards.

Here is an example showing just the critical methods with a single
reference to another component:

type
  TMyComponent = class(TComponent)
  private
    FDataSource: TDataSource;
    procedure SetDataSource(Value: TDataSource);
  protected
    procedure Notification(AComponent: TComponent; Operation:
      TOperation); override;
  published
    property DataSource: TDataSource read FDataSource
      write SetDataSource;
  end;

procedure TMyComponent.SetDataSource(Value: TDataSource);
begin
  if Value <> FDataSource then
    begin
      FDataSource := Value;
      if FDataSource <> nil then
        // Tell the component that we are interested in its fate
        FDataSource.FreeNotification(Self)
    end
end;

procedure TMyComponent.Notification(AComponent: TComponent;
  Operation:TOperation);
begin
  inherited Notification(AComponent, Operation);

  // If this is the component we are referencing then remove our
  //  reference
  if (Operation = opRemove) and (AComponent = FDataSource) then
     FDataSource := nil
end;

5.3. What are component messages?

Component messages are used much like regular Windows messages except
that they are used for notification of events that are only applicable
to Delphi components.  If you have a component that publishes the Font
property the component probably needs to be repainted if any
subproperties in the Font are changed.  Changing the Font property
does not necessarily generate a Windows event but the control still
needs to know about the change.  Component messages serve this
purpose.

It appears that virtual methods could have been used in place of
component messages.  Presumably messages are used in order to keep the
size of the virtual dispatch table from getting out of hand.

The books "Secrets of Delphi 2.0" has descriptions for the individual
component messages.

This is a listing of some of the component messages andwhat they do.
The messages marked "Notification Only" do not pass any useful
information to the message handler and do not expect the message
handler to return a value.

CM_ACTIVATE               (Notification Only)
A form sends itself this message is sent to a form when it becomes the
active form.

CM_CTL3DCHANGED           (Notification Only)
A control sends itself this messsage when its CTL3D Property changes.

CM_DESIGNHITTEST          
Parameters:  TCMDesignHitTest
Return Value:   Appears to be either zero or one.
This message is sent in design mode when the mouse is over the
control.  It appears that the purpose of the message is to determine
if the control wants to process mouse messages while in design mode.
If the return value is one then Delphi lets the control process mouse
messages. If it is zero then the Delphi handles the messages.  If a
control sets this message to one all the time then the popup menu will
never appear.  If the control does not handle this message or returns
zero all the time then the control cannot response to mouse messages
in design mode.

CM_FONTCHANGED  (Notification Only)
Sent to a control when the control's font is changed.

CM_FONTCHANGE             (Notification Only)
A controls sends this message to itself when it receives a
WM_FONTCHANGE message.

CM_PARENTCTL3DCHANGED   (Notification Only)  
Sent to all child controls when a parent (not Owner) receives a
CM_CTL3DCHANGED message. This message is also send when the control is
gets a new parent.

CM_PARENTCOLORCHANGED    (Notification Only)
A Control sends this message to itself when the value of its
ParentColor property changes. This message is also send when the
control is read from a stream or gets a new parent.

CM_PARENTFONTCHANGED      (Notification Only)
Sent to all child controls when a parent (not Owner) receives a
CM_FONTCHANGED message. This message is also send when the control is
read from a stream or gets a new parent.

CM_PARENTSHOWHINTCHANGED  (Notification Only)
A Control sends this message to itself when the value of its
ParentShowHint property changes. This message is also send when the
control is read from a stream or gets a new parent.

CM_WININICHANGE          
Parameters:     Same as for WM_WININICHANGE
Return Value:   None
A control sends itself this message when it receives a WM_WININICHANGE
message.

5.4. My control has focus but it is not gettig keystoke messages.
     What's happening?

If you have published the DragMode property and it is set to
dmAutomatic it is possible for your control to get in a state where it
thinks it is dragging but it really is not.  The CONTROLS.PAS module
has a module local variable called DragControl that is a reference to
the control currently being dragged.  Under certain conditions it is
possible for this variable to not get cleared even though a drag
operation is not underway.  The WndProc procedure to TWinControls
ignores keystroke messages for a control when it thinks dragging is
underway.

------------------------------------------------------------------------
Section 6 - Other Sources of Information

6.1. Are there any books on how to write Delphi components?

The book that has become the standard for writing components is:

"Developing Delphi Components" by Ray Konopka, Coriolis Group Books

While this book not specifically on how to write components it has a
lot of information that is invaluable to the component writer:

"Secrets of Delphi 2" by Ray Lischner, Waite Group Press

Another book on writing components that has information not found in
Konopka's book is

"Programming Delphi Custom Components" by Fred Bulback, M&T Books

6.2. Are there any good web sites with information on how to write
     components?

The largest Delphi web site is "The Delphi Super Site" at

http://sunsite.icm.edu.pl/~robert/delphi

This page has links to many other Delphi sites.

I have found Component source code on the following sites as well:

http://www.coast.net/~jkeller
http://www.pobox.com/~bstowers/delphi

You can also find Delphi sites by using:

Yahoo:  www.yahoo.com
Alta Vista:  www.altavista.digital.com

Unfortunately Web sites have a {*word*193} habit of disappearing or moving.
Please notify the maintainer these addresses are out of date.

------------------------------------------------------------------------
Section 7 - Persistant Objects

7.1. How can I save a complex object containing child objects to the
     .DFM file.

I have tried all sorts of schemes using DefineProperties and
WriteComponents and they all failed to work. As far as I can tell the
only way to do this is to use Delphi's default mechanism to store your
child objects.

A sequence that does work for saving to a stream is:

1. Make all of the classes whose objects you want to save descend from
   TComponent.
2. Make all of the values you want to save published.
3. Within your Register procedure add a call to RegisterComponents
   containing all of the classes you wish to store.
4. Each class that owns child classes needs to overload the procedure
   GetChildren. This procedure is needs to call the procedure passed
   as an argument for each child to be stored. (For Delphi V1 you need
   to override the WriteComponents method and call WriteComponent for
   each child.)

Procedure TMyComponent.GetChildren (Proc : TGetChildProc) ;
  Begin
  Proc (Child1) ;
  Proc (Child2) ;
  ...
  Proc (Childn) ;
  End ;

Getting the objects out of the stream is a little trickier.  Your
parent object may need to overload the GetChildOwner and
GetChildParent functions.  Otherwise Delphi will try to make the child
owned by the form. (In Delphi V1 you need to override the Readstate
method.)

7.2. How can I tell if my contructor is being called for an object
     being loaded from a stream?

csLoading is not set in ComponentState until immediately after the
component is created.  However the component's owner will already have
this set so try

Constructor TMyClass.Create (AOwner : TComponent) ;
  Begin
  If csLoading in AOwner.ComponentState Then
    Begin
    End
  Else
    Begin
    End ;
  End ;

7.3. How can I tell if my component's properties are being saved
     correctly to the form file?

There are a couple of easy ways to view properties as they are stored
in the form file:

1. From design mode in Delphi use click the left mouse button over a
form containing your component the select "View as Text".
Unfortunately if there are any errors in the form file you will not
see anything.

2. From the DOS prompt run the CONVERT program that comes with Delphi.

Stefan Hoffmeister points out that if you copy a control the the
clipboard then you can paste the text representation of the control to
an editor such Notepad. You can edit the control in the editor and
paste it back into to your Delphi application.

------------------------------------------------------------------------
Section 8. Tools for Delphi
8.1. Is there YACC and LEX for Delphi?

There is a YACC and LEX written by Albert Graef
(a...@muwiinfa.geschichte.uni-mainz.de) for Turbo Pascal that works with
Delphi.  This can be found on many shareware sites (search for Pascal
and YACC).

One location is /msdos/turbopas/tply30a1.zip and
/msdos/turbopas/tply30a2.zip in the Simtel archives.

8.2. How can I write code to display JPEG files?

nom...@physik.tu-chemnitz.de has a Pascal library based of the
Independent JPEG Group's free JPEG library.  It is available from the
following locations:

ftp://druckfix.physik.tu-chemnitz.de/pub/nv/
http://www.tu-chemnitz.de/~nomssi/pub/pasjpeg.zip

------------------------------------------------------------------------
Section 9. Basic Programming Techniques
9.1. How do I create a dynamic array of objects?

The easiest way to create an array of objects is to use a TList
control.  I often find it helpful to create specialized classes that
derive from TList. The following class shows how to create a list for
a specific type of object that has a little better error reporting
than the basic TList.  For you own list you may need to implement more
methods.

TListOfMyObject = class (TList)
  Private
    Function GetItems (Index : Ordinal) : TmyObject ;
  public
    Property Items [Index : Ordinal] : TmyObject Read GetItems ;
    Procedure Add (AObject : TmyObject) ;
  End ;

Function TListOfMyObject.GetItems (Index : Ordinal) : TmyObject ;
  Begin
  If Index >= Count Then
    raise Exception.CreateFmt ('Index (%d) outside range 1..%d',
      [Index, Count -1 ]) ;
  Result := Inherited Items [Index] ;
  End ;

Procedure TListOfMyObject.Add (AObject : TmyObject) ;
  Begin
  Inherited Add (AObject) ;
  End ;

9.2. Where is the Wincrt unit in Delphi 2.0?

There is no WinCrt unit in Delphi 2.0. It has been replaced by a
project options setting.  On the linker page there is an option to
create a console application. Check this rather than use the WinCrt
unit.

9.3. What should the base class be for my component?

If you are deriving from an existing component then the class of the
component you wish to derive from should be the base class.  Several
of the registered VCL component classes are immediate ancestors of
corresponding custom classes (e.g. TMemo descends from TCustomMemo).
These custom classes implement most of the functionality of the
control but they do not publish many of the properties that they
define.  In most cases you are probably better off deriving your
component from the custom class rather than the well known registered
class.

If you are creating a component from scratch then TCustomControl is
most likely your best choice.  This is used for visual, windowed
controls.

Other less likely alternatives are:

TGraphicControl - For visual controls that have no associated window.
These cannot receive input focus.

TComponent - Non-visual component.

TWinControl - To create a control that derives from an existing
control that was not created specifically for Delphi.

------------------------------------------------------------------------

Section 10. Advanced Programming Techniques
10.1. Is there a Delphi equivalent to C++'s I/O Stream classes?

Yes and no.  Delphi allows you to create your own "Text-File Device
Driver" which allows you to use standard Delphi I/O procedures for
non-standard I/O streams such as Unix <LF> files or network
connections.  These are not as powerful as C++ I/O Streams but it is
possible to get around their limitations.

The procedure for creating a Text-File Device Driver is reasonably
well documented in the "Object Pascal Langue Guide" and there is one
example in the VCL source in PRINTER.PAS.

Delphi also has stream classes for writing objects to a stream.  These
are not as "general purpose" as C++'s I/O streams.

10.2. How can I get the text equivalent for an enumerated type?

Use the function GetEnumName located in the module TypInfo.

Type
        TMyType = (Value1, Value2) ;
...
Var
        TypeValue : TmyType ;
...
WriteLn (GetEnumName (TypeInfo (TMyType), Ord (TypeValue)) ;

The module TypInfo has many other functions for obtaining information
about types.

The book "Secrets of Delphi 2.0" has a lot of information on how to us
the TypInfo module.

------------------------------------------------------------------------
Section 11. Component Virtual Methods
11.1. How can I find out when my component has had its window handle
      created?

A control's window handle is created by the CreateWnd method.  If you
have processing that needs to be performed after the window handle is
created then you can override CreateWnd and do

Procedure TMyClass.CreateWnd ;
  Begin
  Inherited CreateWnd ; { Don't forget or you'll never get a window
    handle. }

  { Your processing goes here. }
  End ;

11.2. How can I tell when all the components on my form have been
      loaded?

The Loaded method is called for each component on a form after all
controls on the form have been loaded from the stream.

Procedure TMyClass.Loaded ;
  Begin
  Inherited Loaded ; { Clears the csLoading in ComponentState }

  { Your processing goes here. }
  End ;

11.3. Where is the best place to draw my control?

You could intercept the WM_PAINT message and set up your control's
canvas however will VCL do all this work for you if you simply
override the Paint method in your control.

Procedure TMyClass.Paint ;
  Begin
  { You only need to call inherited Paint if your class inherits
    directly from TCustomControl or TGraphicControl.  You may need
    to call this if your control inherits from an existing control. }

  Inherited Paint ;

  { Your processing goes here. }
  End ;

11.4. How do I change the Window Style for my control.

The CreateParams method is used to set up the window style and all the
other arguments that are passed to CreateWindowEx to create the
control's window.To change the window style use something like this
which creates a window with or without a vertical scroll bar.

procedure TMyControl.CreateParams(var Params: TCreateParams) ;
  Begin
  Inherited CreateParams (Params) ;
  IF IWantAScrollBar Then
    Params.Style := Params.Style OR WS_VSCROLL
  Else
    Params.Style := Params.Style AND NOT WS_VSCROLL ;
  End ;

------------------------------------------------------------------------
Section 12. Windows API Functions

12.1. I am trying to scroll the contents of my control but I get an
      ugly flicker effect.  How can I eliminate this?

The easiest way to scroll the elements of a control is to change all
of their coordinates then to force a repaint of the control.
Unfortunately this produces the flicker effect.

The best way to reduct this flickering is to use the ScrollWindow or
ScrollWindowEx Windows API function.

Another source of flickering can be from Windows using two messages to
paint: WM_PAINT and WM_ERASEBKGND.  You may want to intercept all of
the WM_ERASEBKGND messages and do all of your painting, including the
background, in response to WM_PAINT messages in the Paint method.

12.2. How can I restart windows?

Use the ExitWindowsEx function.

12.3. How can I batch updates to when changing the appearance of my
      control?

Sending the WM_SETREDRAW message to your control can either set or
clear the flag used to determine if a control is redrawn.

------------------------------------------------------------------------
Section 13. Control Borders
13.1. How come my control does not have a 3D border even when I have
      CTL3D set to True?

CTL3D has no effect unless csFramed is set in ControlStyle.  Try
something like this inside your constructor:

  ControlStyle := ControlStyle + [csFramed] ;

13.2 How do I implement a BorderStyle property?

The trick to having a control border is that the border must be
created when the control's Window handle is created.

FBorderStyle : TBorderStyle ;
Procedure SetBorderStyle (Style : TBorderStyle) ;
Property BorderStyle : TBorderStyle Read FBorderStyle write
  SetBorderStyle ;
procedure CreateParams(var Params: TCreateParams) ; Override ;

procedure TMyControl.CreateParams(var Params: TCreateParams) ;
  Begin
  Inherited CreateParams (Params) ;
  If FBorderStyle = bsSingle Then
    Params.Style := Params.Style Or WS_BORDER
  Else
    Params.Style := Params.Style And Not WS_BORDER ;
  End ;

Procedure TMyControl.SetBorderStyle (Style : TBorderStyle) ;
  Begin
  IF Style <> FBorderStyle Then
    Begin
    FBorderStyle := Style ;
    { Create  new window handle for the control. }
    RecreateWnd ;
    End ;
   End ;

------------------------------------------------------------------------
Section 14. Component Styles

14.1. How do I stop my control from flickering when it gets repainted?

If you do not include csOpaque in ControlStyle then Invalidate calls
will cause the control's background to be erased.  If you draw your
control's background in the Paint method then you should do this in
your constructor:

ControlStyle := ControlStyle + [csOpaque] ;

From Max Nilson                                  

Another possible cause of flickering can be found by examining the
TWinControls WM_ERASEBKGND handling. Whenever this message is received
by a windowed control the VCL erases the controls background to its
default color. If your controls is a TWinControl decendant, and draws
itself in something other than the default background color (e.g. a
bitmap), then this can cause some flickering as the background is
cleared and then you repaint it again.

By using the following method in your component you inform Windows
that you will handle _all_ of the necessary drawing yourself. You just
have to ensure that you do paint _all_ of your controls surface,
because anything you don't repaint will contain random garbage
pixels. This will also speed your control (very slightly 8-) by saving
one rectangle fill operation.

type
  TMyComponent = class(TWinControl)
  ...
  protected
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd);
      message WM_ERASEBKGND;
  ...
  end;

procedure TBMyComponent.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
 Message.Result := 0
end;

------------------------------------------------------------------------
Section 15. Windows Messages
15.1. How come my control does not get keystroke messages for the
      arrow keys?

To receive keystroke messages for arrow keys you have to handle the
WM_GETDLGCODE messages.  In the messages handler return
DLGC_WANTARROWS. If you do not handle WM_GETDLGCODE Windows intercepts
the arrow keys and uses them to move among controls.

From Max Nilson                                  

To receive keystroke messages for arrow keys you have to handle the
CM_WANTSPECIALKEY messages. The CM_WANTSPECIALKEY allows a much more
'fine grained' way of deciding if you require a special key than
responding to the WM_GETDLGCODE messages. The control message
CM_WANTSPECIALKEY is sent to a control whenever a special key is being
handled.

These special keys are VK_TAB, VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN,
VK_RETURN, VK_EXECUTE, VK_ESCAPE and VK_CANCEL. If the message result
is non zero then the key is passed onto the KeyPress method for you to
handle, otherwise it is passed onto the controls parent form for
Delphi's standard navigation handling. The Delphi standard navigation
is where Delphi handles the Tab, Shift-Tab and arrow key motion
between controls, without needing to use the default Windows dialog
manager at all!

A simple example:

type
  TMyComponent = class(TWinControl)
  ...
  protected
    procedure CMWantSpecialKey(var Message: TCMWantSpecialKey);
      message CM_WANTSPECIALKEY;
  ...
  end;

procedure TMyComponent.CMWantSpecialKey(var Message: TCMWantSpecialKey);
begin
  inherited;

  // We want to heanle the left arrow ourselves
  if Message.CharCode = VK_LEFT then
    Message.Result := 1;
end;

The 'fine grained' I refered to above comes from this ability to
examine a specific keypress and decide if you need to handle the key
your self, or allow it to continue on into Delphi's handler. If you
had a single control with three images, for example, you could allow
the left and right arrows to more back and forth between them, and
also let the user to move onto the next tab stop from the last image
on your control, by allowing Delphi to handle the keypress instead of
your internal control handling.

15.2. Is there an equivalent of Visual Basic's "DoEvents" statement?

Yes.  Application.ProcessMessages

------------------------------------------------------------------------
Copyright 1996 - John Miano

Contributers

Glen Boyd
Stefan Hoffmeister
Ray Konopka
Ray Lischner
Max Nilson
--
Approval for posting does not constitute an endor{*word*224}t.
Articles posted to comp.lang.pascal.delphi.announce are archived at:
WWW:  http://www.infonex.com/~dacroyle/clpda/