Difference between revisions of "How To Write Lazarus Component"

From Lazarus wiki
Jump to navigationJump to search
m (step 2)
Line 15: Line 15:
 
[[Image:How_to_write_lazarus_component_package_maker.png|Package Maker]]
 
[[Image:How_to_write_lazarus_component_package_maker.png|Package Maker]]
  
==Step 2: Create The Unit==
+
==Step 2: Creating The Unit==
 +
 
 +
You can create a new unit or use an existing file.
 +
 
 +
===Create a New Unit===
  
 
* Use the '''Add button > New component'''.
 
* Use the '''Add button > New component'''.
Line 71: Line 75:
 
''Note:'' If you do not see your new component in the component palette, it is most likely that you are not running the re-compiled version of Lazarus. You can set where Lazarus builds to in: Environment -> Environment options -> Files -> Lazarus directory. Instead of calling lazarus directly, you also can use startlazarus, which starts the newly created lazarus, for example the lazarus executable in the ~/.lazarus directory, if you don't have write access to the directory lazarus was installed into.  
 
''Note:'' If you do not see your new component in the component palette, it is most likely that you are not running the re-compiled version of Lazarus. You can set where Lazarus builds to in: Environment -> Environment options -> Files -> Lazarus directory. Instead of calling lazarus directly, you also can use startlazarus, which starts the newly created lazarus, for example the lazarus executable in the ~/.lazarus directory, if you don't have write access to the directory lazarus was installed into.  
  
==Step 2.2: Add an existing Unit==
+
===Add an existing Unit===
  
 
If you already had a unit you can add it to the package:
 
If you already had a unit you can add it to the package:

Revision as of 01:39, 28 June 2011

Introduction

This is a basic guide on how to build components. It was tested on Windows 7 running Lazarus 0.9.30.

Step 1: Create The Package

  • On the Lazarus IDE menu, click Package > New Package to run the Package Manager.

package menu.png

  • A Save dialog file will apear. Chose a folder and a filename and press save. If the IDE promt for using lowercase filenames press 'yes'.
  • Congratulations: You have just created your first package!

Package Maker

Step 2: Creating The Unit

You can create a new unit or use an existing file.

Create a New Unit

  • Use the Add button > New component.

package new component.png

  • Choose a component like TComboBox.
  • Choose customcontrol1.pas as filename.
  • Click OK.

<delphi>unit CustomControl1;

{$mode objfpc}{$H+}

interface

uses

 Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls;

type

 TCustomControl1 = class(TComboBox)
 private
   { Private declarations }
 protected
   { Protected declarations }
 public
   { Public declarations }
 published
   { Published declarations }
 end;

procedure Register;

implementation

procedure Register; begin

 RegisterComponents('Standard',[TCustomControl1]);

end;

end.</delphi>

  • Install the package by clicking the 'install' button in the top of the package editor.

package install.png

  • Then the IDE will ask you, if the IDE should be rebuilt. Say yes.

package rebuild.png

  • Restart Lazarus and see your new component in the component palette. Congratulations: You have just installed your first package with your first package component.

package installed.png

Note: If you do not see your new component in the component palette, it is most likely that you are not running the re-compiled version of Lazarus. You can set where Lazarus builds to in: Environment -> Environment options -> Files -> Lazarus directory. Instead of calling lazarus directly, you also can use startlazarus, which starts the newly created lazarus, for example the lazarus executable in the ~/.lazarus directory, if you don't have write access to the directory lazarus was installed into.

Add an existing Unit

If you already had a unit you can add it to the package:

package existing unit.png

  • Click the Add button, goto the Add Files tab. At the Unit file name, browse to your existing file. Click the Add files to package. If the packet manager complains that the unit is not in the unitpath, click yes to add the directory to the unit path.
  • Click the Add button again, goto the Add Files tab and browse to the .lrs file and click OK (See Step 3 about creating this icon file).
  • Click the Add button again, goto the New Requirement tab, in the Package name select LCL and click OK.

End result should look like this:

Package Maker
  • Click on under the Files tree in the Package Manager. In the File Properties, make sure Register unit is checked.
  • Click the Options button. Select the IDE Integration tab. At the Package Type make sure Designtime and Runtime is selected.
  • Click the Compile button to check to see if the files compile without errors.
  • Click the Install button, Lazarus will rebuild and restart automatically.

The component is created and ready to be used:

Component Created

Step 3: Create Icons For The Package

Size of PNG file should be 24x24. The icon format used is .lrs.

Using Lazarus Image Editor

You can use Lazarus Image Editor to create the icons in .lrs format.

Using lazres

You may need to compile lazres at first use. Simply open the lazres.lpi in the IDE and at the menu click run > build.

Create lrs file

To create the lrs file run:

~/lazarus/tools/lazres samplepackage.lrs TMyCom.png

Where samplepackage is the name of your package and TMyCom is the name of your component.

You can add more than one image to the lrs file by appending the image filename at the end. Eg. ~/lazarus/tools/lazres samplepackage.lrs TMyCom.png TMyOtherCom.png ...

Sample

Following is a sample of the resulting samplepackage.lrs file.

LazarusResources.Add('TMyCom','PNG',[
  #137'PNG'#13#10#26#10#0#0#0#13'IHDR'#0#0#0#24#0#0#0#24#8#2#0#0#0'o'#21#170#175
  +#0#0#0#4'gAMA'#0#0#177#143#11#252'a'#5#0#0#0'|IDAT8O'#237#212#209#10#192' '#8
  +#5'P'#247#231#251's'#215#138#133#164#166'\'#220#195'`'#209'c'#157'L'#173#131
  +#153#169'd4'#168'dP'#137'r_'#235'5'#136'@Zmk'#16'd9'#144#176#232#164'1'#247
  +'I'#8#160'IL'#206'C'#179#144#12#199#140'.'#134#244#141'~'#168#247#209'S~;'#29
  +'V+'#196#201'^'#10#15#150'?'#255#18#227#206'NZ>42'#181#159#226#144#15'@'#201
  +#148#168'e'#224'7f<@4'#130'u_YD'#23#213#131#134'Q]'#158#188#135#0#0#0#0'IEND'
  +#174'B`'#130
]);

Recompiling Packages

You need to rebuild the package everytime you make changes to the mycom.pas file. To rebuild the package, open the samplepackage.lpk file in the Package Manager and click the Install button.

Removing Packages

  • To remove installed components, on the IDE menu, click Package > Configure installed packages. The following image shows the Installed Packages tool.
Installed Components
  • Select the package you want to uninstall and click Uninstall selection.

If something goes wrong with a package (eg package directory is deleted without first uninstalling it), Lazarus may not allow you to uninstall packages. To fix the problem, at the IDE menu click Tools > Build Lazarus. Lazarus will rebuild all packages and restart. You should now be able to uninstall problematic packages.

Enhancing mycom.pas

  • The codes in mycom.pas above gives you the basics on what you need to create a component. The following is an enhanced version with some tips on how to write procedures and events for components.
  • The OnChange2 shows how to create events
  • The OnSample shows how to create custom events
  • MyText and MyText2 shows different ways to write properties.
  • You can use TComboBox instead of TCustomComboBox as the base class, which publishes all properties as TComboBox.
  • If TCustomComboBox is used as the base class, you'll notice a lot of properties and events will be missing at the Object Inspector in the IDE. To add those properties and events just copy and paste the properties as listed below // properties from TComboBox. These list of properties can be got from the TComboBox declaration in the StdCtrls unit. Omit any property which you want to handle yourself.

<delphi> unit mycom;

{$mode objfpc}{$H+}

interface

uses

 Classes, SysUtils, StdCtrls, Forms, Dialogs,
 LCLType,LCLIntf,lresources,LCLProc;

type

 TSampleEvent = procedure(MyText: String) of Object;
 TMyCom = class (TCustomComboBox)
 private
   FMyText: String;
   FOnChange2: TNotifyEvent;
   FOnSample: TSampleEvent;
 public
   constructor Create(TheOwner: TComponent); override;
   procedure CreateWnd; override;
   procedure Change; override;
 protected
   function GetMyText2: String;
   procedure SetMyText2(MyText: String);
 published
   property MyText: String read FMyText write FMyText;
   property MyText2: String read GetMyText2 write SetMyText2;
   property OnChange2: TNotifyEvent read FOnChange2 write FOnChange2;
   property OnSample: TSampleEvent read FOnSample write FOnSample;
   
   // properties from TComboBox
   property Align;
   property Anchors;
   property ArrowKeysTraverseList;
   property AutoComplete;
   property AutoCompleteText;
   property AutoDropDown;
   property AutoSelect;
   property AutoSize;
   property BidiMode;
   property BorderSpacing;
   property CharCase;
   property Color;
   property Ctl3D;
   property Constraints;
   property DragCursor;
   property DragMode;
   property DropDownCount;
   property Enabled;
   property Font;
   property ItemHeight;
   property ItemIndex;
   property Items;
   property ItemWidth;
   property MaxLength;
   property OnChange;
   property OnChangeBounds;
   property OnClick;
   property OnCloseUp;
   property OnContextPopup;
   property OnDblClick;
   property OnDragDrop;
   property OnDragOver;
   property OnDrawItem;
   property OnEndDrag;
   property OnDropDown;
   property OnEditingDone;
   property OnEnter;
   property OnExit;
   property OnGetItems;
   property OnKeyDown;
   property OnKeyPress;
   property OnKeyUp;
   property OnMeasureItem;
   property OnMouseDown;
   property OnMouseMove;
   property OnMouseUp;
   property OnStartDrag;
   property OnSelect;
   property OnUTF8KeyPress;
   property ParentBidiMode;
   property ParentColor;
   property ParentCtl3D;
   property ParentFont;
   property ParentShowHint;
   property PopupMenu;
   property ReadOnly;
   property ShowHint;
   property Sorted;
   property Style;
   property TabOrder;
   property TabStop;
   property Text;
   property Visible;    
 end;

procedure Register;

implementation

procedure Register; begin

 RegisterComponents('Sample',[TMyCom]);

end;

constructor TMyCom.Create(TheOwner: TComponent); begin

 inherited Create(TheOwner);
 Self.Style := csDropDownList;

end;

procedure TMyCom.CreateWnd; begin

 inherited CreateWnd;
 Items.Assign(Screen.Fonts);

end;

procedure TMyCom.Change; begin

 inherited;
 if Assigned(FOnChange2) then FOnChange2(Self);
 if Assigned(FOnSample) then FOnSample(FMyText);

end;

function TMyCom.GetMyText2: String; begin

 Result:=FMyText;

end;

procedure TMyCom.SetMyText2(MyText: String); begin

 FMyText:=MyText;

end;

initialization

 {$I samplepackage.lrs}

end. </delphi>

Using embedded (visual) components

It's possible to use standard components embedded in your own components (look for example at TLabeledEdit or TButtonPanel). Let's say you want to create a custom panel with a TLabel on it. With the steps described above the base package and source files can be created. Now to add a TLabel to the component, do the following:

  • Add a private attribute for the label component (FEmbeddedLabel: TLabel;).
  • Add a published read-only property for the label component (property EmbeddedLabel: TLabel read FEmbeddedLabel;)
  • Create the label in the component's (overridden) constructor (FEmbeddedLabel := TLabel.Create(self); )
  • Set the parent of the component (FEmbeddedLabel.Parent := self;)
  • If the component to be embedded is not a 'subcomponent' by default (like TBoundLabel, TPanelBitBtn etc.) then add the call to SetSubComponent. This is necessary for the IDE so it knows that it has to store the properties of the embedded component as well. TLabel is not a subcomponent by default so the call to the method must be added (FEmbeddedLabel.SetSubComponent(true);).

To sum it up you would get something like this (only the essential parts are shown):

<delphi> TEnhancedPanel = class(TCustomControl) private

 { The new attribute for the embedded label }
 FEmbeddedLabel: TLabel;
 

public

 { The constructor must be overriden so the label can be created }
 constructor Create(AOwner: TComponent); override;
 

pubished

 { Make the label visible in the IDE }
 property EmbeddedLabel: TLabel read FEmbeddedLabel;

end;

implementation

constructor TEnhancedPanel.Create(AOwner: TComponent); begin

 inherited Create(AOwner);
 // Set default width and height
 with GetControlClassDefaultSize do
   SetInitialBounds(0, 0, CX, CY);
 // Add the embedded label
 FEmbeddedLabel := TLabel.Create(Self); // Add the embedded label
 FEmbeddedLabel.Parent := self;         // Show the label in the panel
 FEmbeddedLabel.SetSubComponent(true);  // Tell the IDE to store the modified properties
 FLabel.Name := 'EmbeddedLabel';        
 FLabel.Caption := 'Howdy World!';
 // Make sure the embedded label can not be selected/deleted within the IDE
 FLabel.ControlStyle := FLabel.ControlStyle - [csNoDesignSelectable];
 
 // Set other properties if necessary
 //...
 

end; </delphi>

Using custom paint procedure

Yu can always subclass a component inside your program. For example, this implements a custom Paint procedure to a TLabel:

<delphi> type

 TMyLabel = class(TLabel)
   public
     procedure Paint; override;
 end;

{...} implementation {...} procedure TMyLabel.Paint; begin

 // your code to implement Paint, for example
 Canvas.TextOut(0,0,Caption);

end; </delphi>

Now you can create a MyLabel inside your program, at run time, with that overridden Paint procedure instead of the standard one.

For most components, and for most methods, it would be recommendable to call inherited procedure iniside it:

<delphi> procedure TMyLabel.Paint; begin

 inherited Paint;   /////////////////////
 // your code to implement Paint, for example
 Canvas.TextOut(0,0,Caption);

end; </delphi>

But inherited behavior is not desirable in this case, since the second writing action would overlap the first (inherited) one.

Reference

You can post question regarding this page here

Author

 Original Author