The Delphi Component Writing FAQ

 
Edited By:      John M. Miano	miano@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?
11.4.How do I change the Window Style for 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.2How 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 Debugger.
  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 ; 
    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: 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: 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:

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 keystroke 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 nasty 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 (ag@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?

nomssi@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 handle 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 

Contributors 

Glen Boyd 
Stefan Hoffmeister 
Ray Konopka 
Ray Lischner 
Max Nilson