Better components writing for KOL and MCK.
Copyright (C) By Vladimir Kladov.

The purpose of the given article is to simplify learning by you, components writer, how to write components for KOL and MCK better. I decided to write this text because it seems necessary for those of you who want to write additional components to use it with KOL and MCK projects but still do not know how to do this or how to do this better. Especially what is concerning visual components, based on the TControl object. In most cases writers make a mistake, deriving their new visual object from TObj and embedding TControl into it as a private data field. In result, they have to provide all interface methods and properties by theirself, increasing code size and making their objects less power than TControl itself. Here I explain how to derive TControl based objects directly from TControl, making it more flexible and powerful, and without too many additional code.

type
  PMyObj = ^TMyObj;
  TMyObj = object( TObj )
  private
  protected
  public
  end;

function NewMyObj: PMyObj;

.... {implementation of TMyObj}

function NewMyObj: PMyObj;
begin
  new( Result, Create );
  { your code here to initialize Result }
end;

1. Writing non-visual "components".

This is very simple. You just derive your object from TObj, and define a constructing function like NewMyObj({parameters...}):PMyObj, which creates an instance of the object and initialize its state in accordance with parameters passed (if any). All other work is the same as usual when you create your own class in VCL.

As usual, it is possible to use virtual destructor
Destroy to release resources and memory allocated by the object when it is destroyed. Just remember, that a destructor must be declared with directive virtual but not override unlike it is done usually for classes. And do not forget to call inherited destructor. Also note, that for objects, all ANSI string fields must be assigned to empty string in the destructor, because unlike for classes, those are not released automatically.

type
  PMyControl = ^TMyControl;
  TMyControl = object( TControl )
  private
  protected
  public

   
destructor Destroy; virtual;
  end;

function NewMyControl: PMyControl;

.... {implementation of TMyObj}


PMyCtlData = ^TMyCtlData;
TMyCtlData = packed record
  prop1: Pointer;
  event1: TOnEvent;
  str1: String;
  obj1: PObj;
  ...
end;

function NewMyControl: PMyControl;

var Data: PMyCtlData;
begin
  Result := _NewControl( ... );

  Data := AllocMem( Sizeof( TMyCtlData ) );
  Result.CustomData := Data;
  { your code here to initialize Result }
end;


{ This destructor will not be called except you do know how to do so. Use the other way as it is described. }
destructor TMyControl.Destroy;
var Data: PMyCtlData;
begin
  Data := CustomData;
  FreeMem( Data.prop1 );
  Data.str1 := '';
  Data.obj1.Free;
  inherited;
end;

2. Writing visual controls.

Since KOL uses old Borland Object Pascal model with objects instead classes, it is not possible to use virtual constructors and override it. So, to create an instance of TControl, it is possible only to call _NewControl function (or something other like _NewCommonControl, or if you control is an extension of one of existing controls, you better call correspondent function, e.g. NewButton). But in such way it is not possible to add new data fields and virtual methods, because all such constructing functions work with TControl virtual methods table and allocate memory exactly for the instance of TControl. Therefore, your TControl-based object can use specially added for such purpose fields CustomData: Pointer and CustomObj: PObj to store desired additional fields, including events. Also, the most of initialization code could be placed into overridden method Init, which is called from the constructor Create.

To use
CustomData pointer to store your additional fields or events, in the constructing function allocate memory for it and fill its fields, and assign it to CustomData. When the object is destroyed, a memory pointed by CustomData, released automatically. But in case when some fields should be released additionally (including assigning empty string to ANSI string values, freeing embedded objects, etc.), it is better to use CustomObj, and to performs such operations in its destructor.

In your object methods, always define
Data variable of type PMyCtlData, assign either CustomData or CustomObj field to it and access all additional object's fields through it (when using CustomObj, you should also typecast it to your data type, because PObj is not compatible with your object pointer type while assigning to). This is only a disadvantage of such method of extending TControl. But advantages are much larger. The main of those is that all the methods and properties of basic TControl object are available and applicable when appropriate to your new control. And this compatibility will be saved in future. I.e. if new methods or properties become available for TControl, these will be automatically accessible in your TControl descendant object.

Additionally, you can feel free adding your own methods and properties (for the last, having both
read and write clauses defined through methods, when defined). The only restriction is that methods must not be virtual.


type
  PMyControl = ^TMyControl;
  TMyControl = object( TControl )
  private
  protected
  public

  end;

function NewMyControl: PMyControl;

.... {implementation of TMyObj}


PMyCtlObjData = ^TMyCtlObjData;
TMyCtlObjData = object(TObj)
  prop1: Pointer;
  event1: TOnEvent;
  str1: String;
  obj1: PObj;
  ...
  destructor Destroy; virtual;
end;


function NewMyControl: PMyControl;

var Data: PMyCtlObjData;
begin
  Result := _NewControl( ... );

  new( Data, Create );
  Result.CustomData := Data;
  { your code here to initialize Result }
end;


...
destructor TMyCtlObjData.Destroy;
var Data: PMyCtlData;
begin
  FreeMem( prop1 );
  str1 := '';
  obj1.Free;
  ...
  inherited;
end;

In part, you can not override destructor unless special programming trick (created by Vyacheslav Gavrik or similar). I suggest more easy way, which do not require high programming skill or assembler knowledge. The simplest one is using CustomObj, and writing your destruction code in its destructor. Another one is calling method Add2AutoFreeEx at the initialization. This allows to add a method, which will be called in object destructor.

In the figure right of this text you can see a sample of using CustomObj.

 

3. Writing a mirror for non-visual component.

You can create mirror component for your non-visual object to allow installing it onto component palette. You should know for this moment, that MCK components are not actual components, these just to represent its mirrored objects in Delphi IDE environment. All its functionality is only for generating code for MCK-project, which can be compiled and executed at run-time.

To create a mirror for non-visual object, named Txxxx, derive your component from
TKOLObj. It should be named TKOLxxxx. In such case, names for components dropped onto form are provided like xxxx1, xxxx2, etc. And a call of function Newxxxx automatically generated in unitX_N.inc in MCK projects, using your component.

For samples, see other MCK non-visual components. You can find it in mckobjs.pas, located in archive MCK.ZIP.

Functions, which you override, depends on task, what should be solved. Possible tasks are following.

3.1. Providing additional published properties for your object. To do so, declare your property in published section of your MCK mirror and always use procedure (like SetMyProperty) in write clause of property declaration. In this procedure, set internal fields as needed and (important!) call method
Change to ensure that new version of code will be generated in correspondence with changes made. You should also provide assigning this property to your run-time object, by either passing its value to constructing function Newxxxx as a parameter or generating additional Pascal statements. See also topics below how to do this.

3.2. If additional parameters needed. In such case, override SetupParams function. It returns a string, which will be enclosed into parenthesis and inserted following Newxxxx call. For example, if a boolean value FALSE should be passed at the object initialization, your overridden SetupParams should return string 'FALSE'.

procedure TKOLxxxx.SetupFirst(SL: TStringList; const AName, AParent, Prefix: String);
begin

  inherited;
  if MyProp then
    SL.Add( Prefix + AName + '.MyProp := TRUE;' );
end;

3.3. If additional code should be executed. In such case, override SetupFirst or/and SetupLast function. It adds strings, which are correct Delphi Pascal statements, into string list SL, passed as a parameter. E.g., to provide assigning a value TRUE to a property MyProperty of your object, write code similar to shown on a figure right to this topic.

Calling inherited method useful if you are deriving your object not directly from
TKOLObj, but from one of its descendant.

The difference between
SetupFirst and SetupLast is only that SetupLast is called after calling SetupFirst for all other form objects and controls. This means, that code, added in SetupLast will be executed at run time when all other form's objects already exist.

procedure TKOLxxxx.AssignEvents(SL: TStringList; const AName: String);
begin
  inherited;
  DoAssignEvents( SL, AName, [ 'OnMyEvent1', 'OnMyEvent2' ], [ @OnMyEvent1, @OnMyEvent2 ] );
end;

3.4. If additional events should be assigned. In such case, override AssignEvents method, and call there DoAssignEvents as it is shown on a figure. Do not forget to call inherited method while overriding AssignEvents (otherwise code for assigning inherited events will not be added even if assigned at design time).

4. Writing a mirror for visual component.

Main steps are the same as for writing a mirror component for non-visual object. But for visual component, its MCK mirror must be derived from one of
TKOLCustomControl descendant components (or from TKOLCustomComponent itself). Possible additional task could be providing appropriate visual representation of a control at design time, but in most cases this is not so important. And if you could already implement all other functionality with success, this task is not hard to you too. Take an attention though, that three methods are responsible for correct WYSIWYG painting implementation. These are methods Paint, WYSIWIGPaintImplemented and NoDrawFrames.

Also, it is a good practice for all published properties which could affect visual representation of the control at design time to call
Invalidate method in SetMyProperty-like method, responsible for assigning a value to your published property.

 

20-Oct-2002

KOL and MCK Web-Page : http://kol.thaddy.co.uk 
e-mail: bonanzas@online.sinor.ru