How To Write Lazarus Component/es

From Lazarus wiki
Jump to navigationJump to search

Deutsch (de) English (en) español (es) magyar (hu) italiano (it) 한국어 (ko) русский (ru) 中文(中国大陆)‎ (zh_CN)

Esta es una guía sobre como construir componentes.

Paso 1: Crear el paquete

  • En el menú de Lazarus, clic en Paquete > Nuevo Paquete para ejecutar el administrador de paquetes.

package menu.png

  • Un dialogo Guardar aparecerá. Elije una carpeta y un nombre y presiona guardar. Si el IDE pregunta por usar nombres de archivo en minúsculas presiona 'sí'.
  • Felicidades: Has creado tu primer paquete.

Package Maker

Paso 2: Crear la unidad

Puedes crear una nueva unidad o usar un archivo existente. Las dos opciones son desarrolladas debajo.

Crear una nueva unidad

  • Usa el botón Add > Nuevo componente.

package new component.png

  • Escoge un componente tal como TComboBox.
  • Escoge customcontrol1.pas como Nombre de fichero para la unit y CustomControl1 como Nombre de la unit.
  • Ahora ya puedes asignar al componenete un icono y determinar en que solapa de la paleta del IDE de Lazarus aparecera el componente.
  • Click en Crear Nuevo Componente.
unit CustomControl1;

{$mode objfpc}{$H+}

interface

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

type
  TCustomControl1 = class(TComboBox)
  private
    { Declaraciones Privadas}
  protected
    { Declaraciones Protegidas }
  public
    { Declaraciones Públicas }
  published
    { Declaraciones Publicadas }
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Standard',[TCustomControl1]);
end;

end.
  • Instala el paquete haciendo clic en el botón Use -> Install arriba del editor del paquete.

package install.png

  • Ahora el IDE te preguntará ¿Desea reconstruir ahora Lazarus?, le indicamos que [Si].

package rebuild.png

  • Una vez reiniciado el IDE de Lazarus deberiamos poder ver el nuevo componente en la paleta dentro de la solapa adecuada. Felicidades, ya tienes instalado tu primer paquete con tu primer paquete de componente.

package installed.png

Note-icon.png

Nota: Si no ves tu nuevo componente en la paleta, es más que probable que no estés corriendo la versión re-compilada de Lazarus. Puedes establecer donde se construirá Lazarus seleccionando el trayecto en Herramientas -> Opciones -> Entorno -> Archivos -> Directorio de Lazarus (predeterminado para todos los proyectos). En lugar de llamar a Lazarus directamente, puedes además utilizar startlazarus, el cual inicia el nuevo Lazarus recomplilado en caso de que no se tenga acceso al directorio ~/.lazarus en modo escritura.

Añadir una unidad existente

Si ya tienes una unidad creada la puedes añadir al paquete:


package existing unit.png

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

El resultado final debería parecerse a esto:

Package Maker

  • Click bajo el árbol de ficheros en el Gestor de Paquetes. En Propiedades de Fichero, asegúrate que la Register unit se encuentra chequeada.
  • Click en el pulsador de Opciones. Selecciona la solapa de Integración del IDE. En Tipo de Paquete, asegúrate que están seleccionados Designtime y Runtime.
  • Click en el pulsador de Compilar para chequearque todo se compile sin errores.
  • Click en el pulsador de Instalar. Lazarus se reconstruirá y reiniciará automáticamente.

El componente se ha creado y está listo para ser utilizado:

Component Created

Paso 3: Crear iconos para el paquete

Se deben crear iconos con dimensiones 24x24 píxeles y formato de fichero PNG.

FPC es capaz de utilizar ficheros de recurso estandar .rc y recursos compilados .res desde la versión 2.6. Ver Lazarus_Resources#FPC_resources

Por hacer: escribir la descripción de creación de iconos utilizando recursos FPC.

Los .lrs: ver más abajo.

Utilizando el Editor de Imágen de Lazarus

Puedes utilizar el Editor de Imágenes de Lazarus para crear los iconos en formato ".lrs".

Utilizando lazres

lazres se encuentra habitualmente en el directorio tools de Lazarus.

Se necesita compilarlo para su primer uso lazres. Simplemente abre el fichero lazres.lpi en el IDE y haz click en run > build en el menu.

Create lrs file

Para crear el fichero lrs arranca:

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

Donde:

  • samplepackage es el nombre de tu paquete.
  • TMyCom es el nombre de tu componente. El nombre de la imagen debe coincidir con el nombre de tu componente.

Puedes añadir más de una imagen al fichero lrs añadiendo el nombre del fichero de imagen al final. E.g. ~/lazarus/tools/lazres samplepackage.lrs TMyCom.png TMyOtherCom.png ... Y Ejemplo

El siguiente es un ejemplo del fichero samplepackage.lrs resultante.

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
]);

Asegúrate de incluir tu fichero de recursos en la fuente de tu nuevo componente añadiendo lo siguiente al fondo de la unit del componente e incluyendo "LResources" en la claúsula uses.

initialization
  {$I samplepackage.lrs}

Utilizando glazres

GLazRes is the graphical version of lazres that can assemble files into a Lazarus resource file (.lrs). It can be found in the tools directory of a Lazarus installation.

Recompilando paquetes

Es necesario reconstruir el paquete cada vez que se realizan cambios en el fichero mycom.pas . Para reconstruir el paquete ve a Paquete -> Abrir Archivo de Paquete (.lpk)...-> Seleccionar el fichero samplepackage.lpk en el gestor de paquetes y hacer click en el pulsador Usar -> Instalar.

Eliminando paquetes

  • Para eliminar componentes instalados: en el menú del IDE hacer Click en Paquete -> > Instalar/Desinstalar Paquetes. La siguiente imagen muestra la utilidad de Paquetes Instalados.
Installed Components
  • Selecciona el paquete que necesitas desinstalar y haz Click en Desinstalar Selección.

Si algo va mal con un paquete (e.g. el directorio del paquete se ha borrado sin que primeramente se haya desinstalado), entonces Lazarus no permitirá desinstalar los paquetes. Para solucionar el problema se procede a seleccionar Herramientas en el menú del IDE y a continuación Construir Lazarus, lo cual hará que Lazarus reconstruya todos los paquetes y reinicie. A partir de ahí ya debería permitirte desinstalar los paquetes problemáticos.

Mejorando mycom.pas

  • El código en mycom.pas te muestra lo básico que necesitas para crear un componente. Lo siguiente es una mejora con algunos tips sobre como escribir procedimientos y eventos para componentes.
  • ElOnChange2 muestra como crear eventos.
  • El OnSample muestra como crear eventos personlizados.
  • MyText y MyText2 muestran caminos diferentes de escribir propiedades.
  • Puedes utilizar TComboBox en lugar de TCustomComboBox como clase base, la cual publica todas las propiedades como TComboBox.
  • Si se utiliza TCustomComboBox como la clase base, notarás que se pierden un montón de propiedades y eventos en el Inspector de Objetos del IDE. Para añadir estas propiedades y eventos, simplemente copia y pega las propiedades tal como se listan abajo.// properties from TComboBox. Esta lista de propiedades se pueden obtener desde la declaración de TComboBox en la unidad StdCtrls. Omite cualquier propiedad que necesites manegar por ti mismo.
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.

Además notarás que existen en el Inspector de Objetos del IDE algunos elementos no declarados y posiblemente innecesarios. Para removerlos, los que no necesites, puedes declararlos en la sección Published como simples variables. Por ejemplo:

Published
... 
    property Height: Integer;
    property Width:  Integer;
...

Utilizando componentes (visuales) embebidos

Puedes utilizar componenetes estandar embebidos dentro de tus propios componentes (mira por ejemplo en TLabeledEdit o TButtonPanel).

Digamos que necesitas crear un panel personalizado con un TLabel en el mismo. Con los pasos descritos se pueden crear el paquete base y los ficheros fuente.

Ahora haz lo siguiente para añadir un TLabel al componente:

  • Añade un atributo para el componente label(FEmbeddedLabel: TLabel;).
  • Añade una propiedad publicada de solo lectura para el componente label (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 (solamente se muestran las partes esenciales):

TEnhancedPanel = class(TCustomControl)
private
  { El nuevo atributo para el label embebido }
  FEmbeddedLabel: TLabel;
  
public
  { El contructor debe ser overriden para que el label pueda crearse }
  constructor Create(AOwner: TComponent); override;
  
published
  { Hacer el label visible en el IDE }
  property EmbeddedLabel: TLabel read FEmbeddedLabel;
end;

implementation

constructor TEnhancedPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  // Establece el ancho y alto por defecto
  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];
  
  // Establecer otras propiedades si es neceario
  //...
  
end;

Using custom paint procedure

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

type
  TMyLabel = class(TLabel)
    protected
      procedure Paint; override;
  end;
{...}
implementation
{...}
procedure TMyLabel.Paint;
begin
  // your code to implement Paint, for example
  Canvas.TextOut(0,0,Caption);
end;

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 inside it:

procedure TMyLabel.Paint;
begin

  inherited Paint;   /////////////////////

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

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

Integrando el componente con el IDE

Editores de propiedades

Los editores de propiedades proveen diálogos personalizados para editar propiedades en el Inspector de Objetos. Para la mayor parte de las propiedades, tales como cadenas, listados de cadenas, imágenes, tipos enumerados y otros, hay ya editores de propiedades por defecto, pero pero si un componente personalizado tiene cualquier tipo de propiedad especial se hace necesario un diálogo personalizado para editar la propiedad.

Cada editor de propiedad es una clase, la cual debe descender de TPropertyEditor o de uno de sus descendientes e implementar métodos de esta clase base. Deberían registrase en el procedimiento 'Register' utilizando la función RegisterPropertyEditor de la unidad PropEdits. Es un estandar nombrar a los editores de propiedades con el nombre de la propiedad seguido por 'Property', por ejemplo TFieldProperty para el editor de propiedades de la propiedad TField.

  TPropertyEditor = class
  public
    function  AllEqual: Boolean; Virtual;
    function  AutoFill: Boolean; Virtual;
    procedure Edit; Virtual;     // Activated by double-clicking the property value
    procedure ShowValue; Virtual; // Activated by control-clicking the property value
    function  GetAttributes: TPropertyAttributes; Virtual;
    function  GetEditLimit: Integer; Virtual;
    function  GetName: ShortString; Virtual;
    procedure GetProperties(Proc: TGetPropEditProc); Virtual;
    function  GetHint(HintType: TPropEditHint; x, y: integer): String; Virtual;
    function  GetDefaultValue: AnsiString; Virtual;
    procedure GetValues(Proc: TGetStrProc); Virtual;
    procedure SetValue(const NewValue: AnsiString); Virtual;
    procedure UpdateSubProperties; Virtual;
    function  SubPropertiesNeedsUpdate: Boolean; Virtual;
    function  IsDefaultValue: Boolean; Virtual;
    function  IsNotDefaultValue: Boolean; Virtual;
    // ... shortened
  end;


Un buen ejemplo para un editor de propiedades es el de TFont.

Uno de los casos más habituales de editores de propiedades es el de propiedades que son clases. Debido a que classes tiene bastantes campos y puede tener una variedad de formatos no es posible para Lazarus tener el campo de edición del inspector de objetos capaz de editarlo, tal como es realizado para cadenas y tipos numéricos.

Para clases, una convención es tener el valor del campo mostrado permanentemente de la clase entre paréntesis, por ejemplo "(TFont)" y el pulsador "..." muestra un diálogo para editar esta clase. Este comportamiento, excepto para el diálogo, se implementa por medio de un editor estandar de de propiedades para clases llamado TClassPropertyEditor, del cual puede heredar cuando se escriben editores de propiedades para clases:

TClassPropertyEditor = class(TPropertyEditor)
public
  constructor Create(Hook: TPropertyEditorHook; APropCount: Integer); Override;
  function GetAttributes: TPropertyAttributes; Override;
  procedure GetProperties(Proc: TGetPropEditProc); Override;
  function GetValue: AnsiString; Override;
  property SubPropsTypeFilter: TTypeKinds Read FSubPropsTypeFilter
                                         Write SetSubPropsTypeFilter
                                       Default tkAny;
end;

Volviendo al ejemplo de TFont, heredando desde TClassPropertyEditor already offers part of the desired behavior and then the TFontPropertyEditor class only needs to implement showing the dialog in the Edit method and set the attributes for the editor:

  TFontPropertyEditor = class(TClassPropertyEditor)
  public
    procedure Edit; Override;
    function  GetAttributes: TPropertyAttributes; Override;
  end;

procedure TFontPropertyEditor.Edit;
var 
  FontDialog: TFontDialog;
begin
  FontDialog := TFontDialog.Create(NIL);
  try
    FontDialog.Font    := TFont(GetObjectValue(TFont));
    FontDialog.Options := FontDialog.Options + [fdShowHelp, fdForceFontExist];
    if FontDialog.Execute then SetPtrValue(FontDialog.Font);
  finally
    FontDialog.Free;
  end;
end;

function TFontPropertyEditor.GetAttributes: TPropertyAttributes;
begin
  Result := [paMultiSelect, paSubProperties, paDialog, paReadOnly];
end;

Editores de componentes

Los editores de componentes controlan el comportamiento de los mismos en cuanto a clickeo derecho y doble click en el diseñador de formularios.

Cada componente del editor es una clase, la cual debería descender de TComponentEditor o uno de sus descendientes e implementar métodos para esta clase base. Se deberían registrar en el procedimiento 'Register' utilizando la función RegisterComponentEditor de la unidad ComponentEditors. Es un estandar para nombrar los editores de componentes con el nombre del componente seguido por 'Editor', por ejemplo TStringGridComponentEditor para la propiedad de editior del componente TStringGrid. Although user component editors deberían estar basados en TComponentEditor, la mayoría de sus métodos son actualmente un ancestor, por lo que es necesario conocer además TBaseComponentEditor:

  TBaseComponentEditor = class
  protected
  public
    constructor Create(AComponent: TComponent;
                       ADesigner: TComponentEditorDesigner); Virtual;
    procedure Edit; Virtual; Abstract;
    procedure ExecuteVerb(Index: Integer); Virtual; Abstract;
    function  GetVerb(Index: Integer): String; Virtual; Abstract;
    function  GetVerbCount: Integer; Virtual; Abstract;
    procedure PrepareItem(Index: Integer; const AnItem: TMenuItem); Virtual; Abstract;
    procedure Copy; Virtual; Abstract;
    function  IsInInlined: Boolean; Virtual; Abstract;
    function  GetComponent: TComponent; Virtual; Abstract;
    function  GetDesigner: TComponentEditorDesigner; Virtual; Abstract;
    function  GetHook(out Hook: TPropertyEditorHook): Boolean; Virtual; Abstract;
    procedure Modified; Virtual; Abstract;
  end;

El método más importante de un editor componente es Edit, el cual se llama cuando se hace doble click sobre el componente. Cuando se invocael menú contextual del componente se realiza la llamada a los métodos GetVerbCount y GetVerb para construir el menú. Si se selecciona uno de los verbs (que significa elementos del menú en este caso) entonces se llama a ExecuteVerb. Existe un componente de editor por defecto (TDefaultEditor) el cual implementa Edit para buscar en las propiedades del componente la más apropiada para ser editada. Usualmente escoge un evento, el cual se edita añadiendo su estructura de código en el editor de código y estableciendo el cursor a ubicar al cual añadir código.

Otros métodos importantes para TBasComponentEditor son:: ExecuteVerb(Index), el cual ejecuta uno de los elementos extra del menú ubicado en el menu popup del click derecho; GetVerb – Para retornar el nombre de cada elemento extra del menú popup. Ten en cuenta que es responsabilidad del editor de componente ubicar special menu item caption characters like & to create a keyboard accelerator and “-” to create a separator; GetVerbCount – Returns the amount of items to be added to the popup menu. The index for the routines GetVerb and ExecuteVerb is zero based, going from 0 to GetVerbCount – 1; PrepareItem – Called for each verb after the menu item was created. Allows the menu item to be customized such as by adding subitems, adding a checkbox or even hiding it by setting Visible to false; Copy - Called when the component is being copied to the clipboard. The component data for use by Lazarus will always be added and cannot be modified. This method is instead for adding a different kind of clipboard information to paste the component in other applications, but which won't affect the Lazarus paste.


Un simple e interesante ejemplo es el editor de componente TCheckListBox el cual crea un diálogo de edición. Más conveniente que implementar todos los métodos desde TBaseComponentEditor es heredarlos desde TComponentEditor, y esto es lo que TCheckListBoxEditor hace. Esta clase base añade implementaciones vacía para la mayor parte de los métodos y agunos por defecto para otros. Para Edit llama a ExecuteVerb(0), por lo que si el primer elemento es idéntico a la acción doble click, la cual es una convención para editor, no habrá necesidad de implementar Edit. Esta acción básica para doble-click y el primer elemento de del menú es a menudo un diálogo, y para TCheckListBox se realiza además:

  TCheckListBoxComponentEditor = class(TComponentEditor)
  protected
    procedure DoShowEditor;
  public
    procedure ExecuteVerb(Index: Integer); override;
    function  GetVerb(Index: Integer): String; override;
    function  GetVerbCount: Integer; override;
  end;

procedure TCheckGroupComponentEditor.DoShowEditor;
var
  Dlg: TCheckGroupEditorDlg;
begin
  Dlg := TCheckGroupEditorDlg.Create(NIL);
  try
    // .. shortened
    Dlg.ShowModal;
    // .. shortened
  finally
    Dlg.Free;
  end;
end;

procedure TCheckGroupComponentEditor.ExecuteVerb(Index: Integer);
begin
  case Index of
    0: DoShowEditor;
  end;
end;

function TCheckGroupComponentEditor.GetVerb(Index: Integer): String;
begin
  Result := 'CheckBox Editor...';
end;

function TCheckGroupComponentEditor.GetVerbCount: Integer;
begin
  Result := 1;
end;

Depurando un componente en tiempo de diseño

Para detectar errores en tiempo de diseño en un (nuevamente creado) dbgcomponent:

  • Abrir proyecto C:\lazarus\ide\lazarus.lpi;
  • Ejecutar proyecto;
  • Establecer punto de interrupción en dbgcomponent in main (1st) app;
  • use dbgcomponent pascal code in second app;
  • step through design-time component code in debug-session; do whatever is necessary;

See also

You can post questions regarding this page here