Make your own free website on Tripod.com
[an error occurred while processing this directive]

Key Objects Library

Key Objects Library

 

Copyright (C) by Vladimir Kladov, 2000-2001


Using the KOL library. Lesson 1: An Introduction.
by Thaddy de Koning

What is KOL?

KOL is a library that enables you to write very small, but full featured WIN32 platform applications using Delphi in an Objected Oriented context. It has always been possible to write small Delphi programs if you do not use the VCL but a procedural approach. KOL is the successor to the maybe better known XCL library and creates even smaller programs. Both KOL and XCL are designed by Vladimir Kladov, with contributions by others.

As I am very impressed with what you can achieve with KOL and as there are few if any examples available save from the KOL documentation, I decided to do a series on KOL programming.

In the process you will also learn a few tricks that are by many considered to be black art or at least only for the high priests of Delphiprogramming. With Kol it's easy!

My intention is both to teach you how to use it and to promote KOL as a viable alternative to the VCL. For this series you need a copy of Kol. You can obtain that by downloading it from:

http://xcl.cjb.net

which is Vladimir Kladov's site.

The sourcecode to this article can be found here:

http:www.thaddy.com/kol_lesson_1.zip

About the Architecture

To achieve such small program size the main goal was to reduce overhead. The VCL has loads of that: convienience at the expense of resources mainly because of Runtime Type Information.

So Vladimir set out to reduce that. He did this in Six main ways:

There are several more reasons and optimizations but in my opinion these are the most important.

Mind you, the architecture is sufficiently open to use just the features you like or want. I for one do not use the system.pas replacements. This is still very much OOP, although some people might want to argue otherwise.

 

Let's get started

Since KOL is a non-visual environment we have to use our imagination as for how a program would look on screen. But since the programs are so small, the compilation time is also extremely short, measured in milliseconds. So basically we can refer to our product in a visual way just because the programs are so small. Hit F9 and it's there!

The first thing we do is start a new project in the usual way.

Compile the empty product and remember the executable size! In Delphi 4.02 its over 283 K.

The next thing we do is remove the references we don't need:

If we open up the project we see:

program Project1;
uses

{Now Remove this} Forms,
{And this} Unit1 in 'Unit1.pas' {Form1};
{Remove this} {$R *.RES}
begin

{Remove this} Application.Initialize;
{Remove this} Application.CreateForm(TForm1, Form1);
{Remove this} Application.Run;
end.

Well, this is basically empty, a bit like a console application, but we do full Windows, so we add a couple of things we really need: The Windows unit, the Messages unit and the KOL unit. To make a fair comparison I will add a form in KOL. I will explain later what is does and how it does it.

We end up with this:

program Project1;
uses
Windows, Messages, Kol;
//not needed! {$R *.RES}
var
  Form1:pControl;
begin
  Form1:=NewForm(nil, 'Form1');
  Run(Form1);
end.

Run this, and you'll notice there isn't much difference. Looks the same, does the same and is utterly useless just the same. Only now our program is just 23k! which seems a bit more reasonable for a useless diskpolluting program, you'll agree.

So, let me explain how it works.

First, all controls, including the form and the application object itself are instances of one and the same Tcontrol object, with is initialized to its visual appearance in its construction function. We do not call Tcontrol.Create, but instead an initialization function that creates the control we want. For this purpose all control instance types have a creator function that starts with 'New' as convention.

For a form, KOL defines the contruction function like this:

function NewForm( AParent: PControl; const Caption: String ): PControl;

Kol allows us to skip an application object if the application is just one form. The VCL has always an implied application object instance defined in the forms unit. So in this case we pass nill as the parent and we saved ourselves an Application Object.

A windows application is centered around a messagepump that sends messages to windows in the application to respond to. This loops until you terminate the application. This is what the 'Run' procedure does: It provides the messageloop to the application. In the pascal version of Kol it looks like this, and isn't very different from the VCL's implementation:

procedure Run( var AppletWnd: PControl );
var App: PControl;
begin
  AppletRunning := True;
  Applet := AppletWnd;
  AppletWnd.CreateWindow;
  while not AppletTerminated do
    AppletWnd.ProcessMessages;
  AppletRunning := False;
  App := AppletWnd;
  Applet := nil;
  App.Perform( WM_CLOSE, 0, 0 );
  App.Free;
  AppletWnd := nil;
end;

But let's not divert too much from using KOL instead of explaining the internals. Suffice to say it works, and even better, almost the entire library is also available in a pure assembly language version for even smaller programs. I suggest you use the pascal version to understand how it works if you so choose, and use the assembler version for your programs. The versions are transparent to the user. The best way to get acquainted is to write a fullfeatured program and explain it as we go along.

So here it is.

Our goal is to write a tray utility that utilizes the sensitivity of the mouse as a burglar alarm.

I first saw this Idea way back in history and I did a version in PowerBasic 2 for DOS, the successor to another Borland product:TurboBasic. By the way: Kol has in many ways similarities to PB/DLL, the Windows version of PowerBasic which also creates incredably small programs, even smaller than Kol but with a lot more work.

First we set up a program framework as we did in the above example:

program RatAlert; {get it?, you will!}

uses Windows, Messages, Kol;

var
  //Applet, {This will be our Application Object}
  //commented by V.K. Applet is the predefined variable,
  //and You should never declare your own one.

  Main, {This will be our Main Form}
  Panel {This is just a panel}
 
:pControl;

Kol Design Rule number one:

All Controls are instances of one and the same object type. That is why we declare them as Pointers to TControl. VCL instances are implied pointers to Tobject derived instances as opposed to Kol where we have to explicitly declare them as pointers to objects.

Popup:pMenu; {A Popup menu to control the application}
TrayIcon:pTrayIcon;{A TrayIcon Object, as provided in the KOL library}

Before we start with our main program let me explain a few other things about Kol:

Kol Design Rule number two:

Kol is not a Visual Development Environment. It is a Rapid Application Development tool,though.

(Note by V.K.: this is not so for now. MCK, which is supplied with KOL, is fully functional visual development environment.)

As we are all used to filling in lots of properties from the object inspector, you will be amazed how good a job Kol does of hiding complexity in a non-visual way. Almost all controls need just a few parameters set to get them up and running. This is one of the strengths of the constructor functions where the controls are initialized to pretty good standard values.

Here's the main program:

 

begin
  Applet:=NewApplet('RatAlert');
{Create a TControl instance as an Application instance}

An Applet serves as the applications main window. It also controls if an application shows itself in the taskbar. This is why we need it. If we used the form as the application object, we won't be able to hide the application very easily from the taskbar once it is started . Now we can do so by simply stating:

  Applet.Hide;

 

First, once our program is active we need a main window that captures the mouse. We can't use Applet, because that would show up in the taskbar.

For now, we will hide the form as well:

Form :=NewForm(Applet,'Rat Alert'); {Create a Form and it's caption}

Form.Hide;

This panel is used as the parent for the PopUp Menu. If we created the menu with the Form as parent, it would be the main menu and not a popup menu, much like the VCL

Panel :=NewPanel(Form, esNone); {Create a panel and set it's edge style, see KOL docs}

So Now we can create our Popup Menu. Because of the Non Visual nature, this is one control that needs more than just a couple of parameters, but it's straightforward once you understand it.

You pass the constructor function the following parameters:

Event handling is done in much the same way as in the VCL, but we have to do a little more work.

As in the VCL, the OnWhatever property is a pointer to a handling method. In the VCL its type is filled in for you. In Kol you have to do it yourself, either by deriving a new Object and overriding the OnWhatEver handler or by converting a normal procedure or a function in a procedure or function of object. The difference being, that a procedure of object has an extra, hidden, parameter that references self and a reference to Its data. In Kol you can write a procedure of object just like a normal procedure. The trick it uses can be used in the VCL as well. Once we've created it we have to cast it to the desired event type - in this case TOnMenuItem- so the compiler knows what to do with it and pass its pointer to the creation function. If you want to know how it works I suggest you look at the Kol sourcecode.

We will call the menuhandler DoPopUpItem for now and implement it later.

Kol provides us with a Utility function that turns a normal function in a method:MakeMethod.

A method is described as a record with a pointer to its data and a pointer to Its code.

Most of the time you simply pass nil as the data parameter.

Kol Design Rule number three

In Kol an Event handler must be explicitly casted to its event type when you use MakeMethod.

PopUp:=NewMenu(Panel,0,['Rat Alert &On', 'Rat Alert O&ff', '-', '&Kill the Rat',''],
  TOnMenuItem( MakeMethod( nil, @DoPopUpItem )));

Set the menuitems to their natural state:

Item 1 is the Off Item, and we are not activated yet, so it should be disabled

PopUp.ItemEnabled[1]:=false;

So Now the TrayIcon. I have provided a resource script that contains two Icons and a WAV sound, but you can ofcourse use your own using the RATALERT.RC file as a template and compile it with BRCC32.

The TrayIcon has a simple mouse handler attached to it that we call DoTrayMouse and implement later. You see that, as opposed to the menu, we do not set the eventhandler in the construction function, but rather assign the Onmouse event later. The Design rule behind this is, that a Tray Icon does not always need a handler to be usefull, whereas a Menu does.

The parameters for the construction function are a parent, and a default Icon.

TrayIcon:=NewTrayIcon(Main,Main.Icon); {Use the default icon}

TrayIcon.OnMouse:=TOnTrayIconMouse(Makemethod(nil,@DoTraymouse));

TrayIcon.ToolTip:='Rat Alert'; {Simple tooltip}

Well, that's basically the whole program except for the implementation of our eventhandlers.

All we need to make it run is:

Run(Applet);

end.

Now let's get back to our eventhandlers. Because I did not forward declare them and I did not derive any descendent objects to implement them, these should be written above the main program body we have just finished, like:

program XXXX

Uses X

handlers go here

begin

Body goes here

end.

The basic task for the program is to start howling when somebody touches our stuff.

When our program is activated, the mouse is captured by our main form, so we implement our basic functionality in a handler that we assign to the main form's OnMouse event later, when its needed.

We use one of the standard Kol Sound routines to play a wave file we have linked into the programs

resource, along with the Icons.Remember that I explained about the hidden self parameter: Here its introduced in the declaration of the procedure. Compare it to the declaration of the event and you'll see why its called 'hidden'.

procedure DoMouseMove(Dummy:Pointer;Sender:pControl;var Mousedata:TmouseEventData);
begin
 
{This plays the sound provided by the supplied resource
  Alternatively you could load it from file into memory or
  even play it from disk by one of the other KOL supplied
  sound routines. See KOL.pas}

  PlaySoundResourcename(hinstance,'ALARM',[poLoop,poNoStopAnotherSound,poNotImportant]);
end;

Our program is hidden, so logically the first thing that the user is confronted with is the Tray Icon itself. So we implement the handler for the TrayIcon first.

Its main task is to popup the Popup menu. This is how it works.

procedure dotraymouse(dummy:pointer;sender:Tobject;Message:Word);
var
  P:Tpoint;
begin
  if message = WM_RBUTTONUP then
  begin
  
{Where's the mouse?}
    getcursorpos(p);
   
{Popup at that location}
    PopUp.PopUp(p.x,p.y);
  end;
end;

So now the popup menu can be visible we can implement its handler.

This is our most important routine:

It works like this:

Since our plan is to use the mouse as an alarm, we have to device a scheme that enables us to activate it without using the mouse itself.

This is easier than delaying the activation in a thread f.e..

So, we show a simple modal dialog, which can be closed by pressing 'Enter', after that, the application is activated as planned.

Menu Items are accessed by Index. Any items that are separators are ignored. So we have to write code for three menuitems.

There are a couple of interesting things to note.

 

procedure DoPopUpItem(Dummy:pointer;Sender:pMenu;Item:Integer);
begin
  case item of
  0:begin
       Messagebox(Main.handle,
       'Rat Alert guards your documents during'
       +#13#10
       +'your absence.'
       +#13#10#13#10
       +'Put the mouse on the objects you whish to guard.'
       +#13#10
       +'Press "Enter" to activate'
       +#13#10
       +'Don''t touch the mouse yourself!'
       +#13#10
       +#13#10
       +'Copyright 1989-2001, Thaddy de Koning',
       'Rat Alert!',
       MB_OK or MB_APPLMODAL or MB_SETFOREGROUND or MB_ICONWARNING);

What we do here is a neat little trick to obtain an active window that is virtually invisible:

We move it off of the visible area of the screen, except for one pixel :-)

Then we change the Icon in the taskbar to indicate that RatAlert is activated.

You an use either the icons supplied in the resource, or any icon you like. Again, as with the sound there are several ways you can do this with KOL. See KOL.pas}

       {Obtain the screen width}
       Main.left:=getsystemmetrics(SM_CXFULLSCREEN)-2;
     
{Activate the window}
       Main.Show;
       {Popup Housekeeping}
       pop.ItemEnabled[0]:=false;
       pop.ItemEnabled[1]:=true;
       {Now, move it almost off the screen}
       setcursorpos(Main.left,Main.top);
       {Make shure we own the mousemessages}
       setcapture(Main.handle);
       {Eat the message queue before connecting,
       otherwise a pending mouse event could still
       be fired!}
       Main.ProcessPendingMessages;
       {At this point we can connect our own mousehandlingroutine}
       Main.OnMouseMove:=TOnMouse(MakeMethod(nil,@DoMouseMove));
       {Change the Icon to indicate activation}
       TrayIcon.Icon:=LoadIcon(hinstance,'MAINICON2');
     end;
  1:begin
       {Kill the sound if it's playing}
       PlaySoundStop;
       {disconnect our mousehandler}
       Main.OnMousemove:=Nil;
       {We really won't want the mouse rightnow}
       releasecapture;
       {Popup Housekeeping}
       PopUp.ItemEnabled[0]:=true;
       PopIp.ItemEnabled[1]:=False;
       {Change the mouse back to its default}
       TrayIcon.Icon:=LoadIcon(hinstance,'MAINICON');
     end;
  2:begin
       {Kill the sound if it's playing}
       PlaySoundStop;
       {disconnect ouur mousehandler}
       TrayIcon.OnMouse:=nil;
       {No mouse please}
       Releasecapture;
       {Close the application}
       Main.Perform(WM_CLOSE,0,0);
     end;
  end;
end;

And this completes the first lesson except for me obfuscating the whole source with comments, so that's why you can download the code without most of the comments. Then you can actually see how small the code is, how elegant Kol works and finally you can compile the code and see what the whole thing looks like. Pretty impressive for a first effort, don't you think?

[Some text here was skipped - by V.K.]

Please let me know what you think of Kol. I personally think it is very usefull and very elegant and deserves a place in the toolkit of almost every Delphi developer.

You can reach me at:

mailto:thaddy@thaddy.com

Thaddy de Koning