Difference between revisions of "LCL Tips"

From Lazarus wiki
Jump to navigationJump to search
(Changing to the more common ==)
 
(8 intermediate revisions by 5 users not shown)
Line 3: Line 3:
 
== Creating a GUI by code ==
 
== Creating a GUI by code ==
  
It is possible to create the GUI (Graphical User Interface) code completely by pascal code in Lazarus. Everything accessible from the IDE is also accessible by code. The example program and unit files below (codegui.lpr and mainform.pas) give you a template you can adapt. The most important part is not forgetting to set the Parent property of the components. The creation of controls inside the form is best done in the constructor of the form:
+
It is possible to create the [[Overview_of_Free_Pascal_and_Lazarus#GUI_apps|GUI]] (Graphical User Interface) code completely by pascal code in Lazarus. Everything accessible from the IDE is also accessible by code. The example program and unit files below (codegui.lpr and mainform.pas) give you a template you can adapt. The most important part is not forgetting to set the [[Parent]] property of the components. The creation of controls inside the form is best done in the constructor of the form:
  
main program file:
+
Main program file:
  
<delphi>program codedgui;
+
<syntaxhighlight lang=pascal>
 +
program codedgui;
  
 
{$MODE DELPHI}{$H+}
 
{$MODE DELPHI}{$H+}
Line 21: Line 22:
 
   Application.CreateForm(TMyForm, MyForm);
 
   Application.CreateForm(TMyForm, MyForm);
 
   Application.Run;
 
   Application.Run;
end.</delphi>
+
end.</syntaxhighlight>
  
 
And a unit containing a form:
 
And a unit containing a form:
  
<delphi>unit mainform;
+
<syntaxhighlight lang=pascal>
 +
unit mainform;
  
 
{$MODE DELPHI}{$H+}
 
{$MODE DELPHI}{$H+}
Line 77: Line 79:
 
end;
 
end;
  
end.</delphi>
+
end.
 +
</syntaxhighlight>
  
 
== Create controls manually without overhead ==
 
== Create controls manually without overhead ==
Line 85: Line 88:
 
'''For Delphians''': Contrary to Delphi the LCL (Lazarus Component Library) allows you to set nearly all properties in any order. For example under Delphi you cannot position a control if it has no parent. The LCL allows this and this feature can be used to reduce overhead.
 
'''For Delphians''': Contrary to Delphi the LCL (Lazarus Component Library) allows you to set nearly all properties in any order. For example under Delphi you cannot position a control if it has no parent. The LCL allows this and this feature can be used to reduce overhead.
  
<delphi>with TButton.Create(Form1) do begin
+
<syntaxhighlight lang=pascal>
 +
with TButton.Create(Form1) do begin
 
   // 1. creating a button sets the default size
 
   // 1. creating a button sets the default size
 
   // 2. change position. No side effects, because Parent=nil
 
   // 2. change position. No side effects, because Parent=nil
Line 95: Line 99:
 
   // 5. Set Parent. Now all the above takes place, but in a single action.
 
   // 5. Set Parent. Now all the above takes place, but in a single action.
 
   Parent:=Form1;
 
   Parent:=Form1;
end;</delphi>
+
end;
 +
</syntaxhighlight>
  
 
When a control has a Parent, then all properties take effect immediately. Without a Parent many properties do nothing more than store the value. And as soon as the Parent is set every property is applied. This is especially true for grand children:
 
When a control has a Parent, then all properties take effect immediately. Without a Parent many properties do nothing more than store the value. And as soon as the Parent is set every property is applied. This is especially true for grand children:
  
<delphi>GroupBox1:=TGroupBox.Create(Self);
+
<syntaxhighlight lang=pascal>
 +
GroupBox1:=TGroupBox.Create(Self);
 
with GroupBox1 do begin
 
with GroupBox1 do begin
 
   with TButton1.Create(Self) do begin
 
   with TButton1.Create(Self) do begin
Line 108: Line 114:
 
   Parent:=Form1;
 
   Parent:=Form1;
 
end;
 
end;
Form1.Show;</delphi>
+
Form1.Show;
 +
</syntaxhighlight>
  
 
Autosizing starts only after every parent is set up and the form becomes visible.
 
Autosizing starts only after every parent is set up and the form becomes visible.
Line 119: Line 126:
  
 
Instead of
 
Instead of
<delphi>with Button1 do begin
+
<syntaxhighlight lang=pascal>
 +
with Button1 do begin
 
   Left:=10;
 
   Left:=10;
 
   Top:=10;
 
   Top:=10;
 
   Width:=100;
 
   Width:=100;
 
   Height:=25;
 
   Height:=25;
end;</delphi>
+
end;
 +
</syntaxhighlight>
 +
 
 
Use
 
Use
<delphi>with Button1 do begin
+
 
 +
<syntaxhighlight lang=pascal>
 +
with Button1 do begin
 
   SetBounds(10,10,100,25);
 
   SetBounds(10,10,100,25);
end;</delphi>
+
end;
 +
</syntaxhighlight>
  
 
Left, Top, Width, Height are calling SetBounds. And every change of position or size invokes recalculation of all sibling controls and maybe recursively the parent and/or the grandchild controls.
 
Left, Top, Width, Height are calling SetBounds. And every change of position or size invokes recalculation of all sibling controls and maybe recursively the parent and/or the grandchild controls.
Line 136: Line 149:
 
When positioning many controls, it is a good idea to disable the recalculation of all auto sizing, aligning, anchoring.
 
When positioning many controls, it is a good idea to disable the recalculation of all auto sizing, aligning, anchoring.
  
<delphi>DisableAlign;
+
<syntaxhighlight lang=pascal>
 +
DisableAlign;
 
try
 
try
 
   ListBox1.Width:=ClientWidth div 3;
 
   ListBox1.Width:=ClientWidth div 3;
Line 143: Line 157:
 
finally
 
finally
 
   EnableAlign;
 
   EnableAlign;
end;</delphi>
+
end;
 +
</syntaxhighlight>
  
 
'''Note''': Every DisableAlign call needs an EnableAlign call. For example if you call DisableAlign two times, you must call EnableAlign twice as well.
 
'''Note''': Every DisableAlign call needs an EnableAlign call. For example if you call DisableAlign two times, you must call EnableAlign twice as well.
Line 151: Line 166:
 
== Creating a non-rectangular window or control ==
 
== Creating a non-rectangular window or control ==
  
One can easily create non-rectangular windows or controls in Lazarus. For this, one can simply call TWinControl.SetShape, with the visible region as a parameter. Note that this will work for both windows and controls, as both TCustomForm and TCustomControl descend from TWinControl. One can also call the lclintf routine SetWindowRgn, which is completely equivalent to calling the SetShape method.
+
One can easily create non-rectangular windows or controls in Lazarus. For this, one can simply call TWinControl.SetShape, with the visible region as a parameter. Note that this will work for both windows and controls, as both TCustomForm and TCustomControl descend from TWinControl. One can also call the LCLIntf routine SetWindowRgn, which is completely equivalent to calling the SetShape method.
  
 
Using SetWindowRgn the code will be similar to this:
 
Using SetWindowRgn the code will be similar to this:
  
<delphi>
+
<syntaxhighlight lang=pascal>
uses lclintf, lcltype;
+
uses LCLIntf, LCLType;
  
 
procedure TForm1.FormCreate(Sender: TObject);
 
procedure TForm1.FormCreate(Sender: TObject);
 
var
 
var
   MyRegion: TRegion;
+
   MyRegion: HRGN;
 
begin
 
begin
 
   MyRegion := CreateRectRgn(0, 0, 100, 100);
 
   MyRegion := CreateRectRgn(0, 0, 100, 100);
Line 166: Line 181:
 
   DeleteObject(MyRegion);
 
   DeleteObject(MyRegion);
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
 +
 
 +
An equivalent code, which uses a higher level TRegion object available in Lazarus 0.9.31+ (note that the previous way of doing things is still supported and will be in the future too), is:
  
An equivalent code, available in Lazarus 0.9.31+, is:
+
<syntaxhighlight lang=pascal>
 +
uses Graphics;
  
<delphi>
 
 
procedure TForm1.FormCreate(Sender: TObject);
 
procedure TForm1.FormCreate(Sender: TObject);
 
var
 
var
Line 183: Line 200:
 
   end;
 
   end;
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
The result of this operation in a window in Mac OS X using the Qt widgetset can be see here:
+
The result of this operation in a window in macOS using the Qt widgetset can be seen here:
  
 
[[Image:non_rectangular_window.png]]
 
[[Image:non_rectangular_window.png]]
Line 202: Line 219:
 
Limitations:
 
Limitations:
  
In Gtk2 a region can only be set after a window is realized. Calling SetWindowRgn in the OnShow event handler doesn't work, the only way seams to be to call it from a timer set with interval 1, for example. Enable the timer in Form.OnShow and disable it in it's OnTimer handler.
+
In Gtk2 a region can only be set after a window is realized. Calling SetWindowRgn in the OnShow event handler doesn't work, the only way seems to be to call it from a timer set with interval 1, for example. Enable the timer in Form.OnShow and disable it in it's OnTimer handler.
  
 
==Simulating Mouse and Keyboard input==
 
==Simulating Mouse and Keyboard input==
  
It is very easy to similate mouse and keyboard input in the LCL, just use the routines from the unit LCLMessageGlue like all widgetset interfaces do. This unit has routines like:
+
It is very easy to simulate mouse and keyboard input in the LCL, just use the routines from the unit LCLMessageGlue like all widgetset interfaces do. This unit has routines like:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
unit LCLMessageGlue;
 
unit LCLMessageGlue;
  
Line 229: Line 246:
 
function LCLSendKeyUpEvent(const Target: TControl; var CharCode: word;
 
function LCLSendKeyUpEvent(const Target: TControl; var CharCode: word;
 
   KeyData: PtrInt; BeforeEvent, IsSysKey: boolean): PtrInt;
 
   KeyData: PtrInt; BeforeEvent, IsSysKey: boolean): PtrInt;
</delphi>
+
</syntaxhighlight>
  
 
==Showing the Virtual Keyboard in Smartphones/tablets==
 
==Showing the Virtual Keyboard in Smartphones/tablets==
Line 235: Line 252:
 
To show the virtual keyboard when a widget receives focus just add the csRequiresKeyboardInput ControlStyle:
 
To show the virtual keyboard when a widget receives focus just add the csRequiresKeyboardInput ControlStyle:
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
constructor TMyTextEditor.Create(AOwner : TComponent);
 
constructor TMyTextEditor.Create(AOwner : TComponent);
 
begin
 
begin
Line 242: Line 259:
 
...
 
...
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
  
 
[[Image:Virtual_keyboard.png]]
 
[[Image:Virtual_keyboard.png]]
Line 250: Line 267:
 
This is very easy, just use a loop to iterate over TWinControl.ControlCount and TWinControl.Controls[Index]. The index is zero based.
 
This is very easy, just use a loop to iterate over TWinControl.ControlCount and TWinControl.Controls[Index]. The index is zero based.
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
procedure TWinControl.WriteLayoutDebugReport(const Prefix: string);
 
procedure TWinControl.WriteLayoutDebugReport(const Prefix: string);
 
var
 
var
Line 259: Line 276:
 
     Controls[i].WriteLayoutDebugReport(Prefix+'  ');
 
     Controls[i].WriteLayoutDebugReport(Prefix+'  ');
 
end;
 
end;
</delphi>
+
</syntaxhighlight>
 +
 
 +
==Responding only once to an event==
 +
 
 +
Sometimes one wants an event handler to be called just once. Say, for example, that you want to do something only the first time a form is activated, once the form and all its controls are fully loaded (which might not be the case when OnCreate is fired).
 +
 
 +
The solution is easy: simply set the corresponding property to Nil at the beginning of the handler:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
procedure TMyForm.FormActivate(const Sender: TObject);
 +
begin
 +
  OnActivate := Nil;
 +
  {... rest of the code here ...}
 +
end;
 +
</syntaxhighlight>
  
[[Category:LCL]]
+
and the event handler won't be called again.

Latest revision as of 08:18, 31 July 2020

Deutsch (de) English (en) français (fr) русский (ru) 中文(中国大陆)‎ (zh_CN)

Creating a GUI by code

It is possible to create the GUI (Graphical User Interface) code completely by pascal code in Lazarus. Everything accessible from the IDE is also accessible by code. The example program and unit files below (codegui.lpr and mainform.pas) give you a template you can adapt. The most important part is not forgetting to set the Parent property of the components. The creation of controls inside the form is best done in the constructor of the form:

Main program file:

program codedgui;

{$MODE DELPHI}{$H+}

uses
  Interfaces, Forms, StdCtrls,
  MainForm;

var
  MyForm: TMyForm;
begin
  Application.Initialize;
  Application.CreateForm(TMyForm, MyForm);
  Application.Run;
end.

And a unit containing a form:

unit mainform;

{$MODE DELPHI}{$H+}

interface

uses Forms, StdCtrls;

type
  TMyForm = class(TForm)
  public
    MyButton: TButton;
    procedure ButtonClick(ASender: TObject);
    constructor Create(AOwner: TComponent); override;
  end;

implementation

procedure TMyForm.ButtonClick(ASender:TObject);
begin
  Close;
end;

constructor TMyForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  //Hint: FormCreate() is called BEFORE Create() !
  //so You can also put this code into FormCreate()
  //(This is not the case when creating components ..)

  Position := poScreenCenter;
  Height := 400;
  Width := 400;

  VertScrollBar.Visible := False;
  HorzScrollBar.Visible := False;

  MyButton := TButton.Create(Self);
  with MyButton do
  begin
    Height := 30;
    Left := 100;
    Top := 100;
    Width := 100;
    Caption := 'Close';
    OnClick := ButtonClick;
    Parent := Self;
  end;

  // Add other component creation and property setting code here
end;

end.

Create controls manually without overhead

Set the Parent as last

For Delphians: Contrary to Delphi the LCL (Lazarus Component Library) allows you to set nearly all properties in any order. For example under Delphi you cannot position a control if it has no parent. The LCL allows this and this feature can be used to reduce overhead.

with TButton.Create(Form1) do begin
  // 1. creating a button sets the default size
  // 2. change position. No side effects, because Parent=nil
  SetBounds(10,10,Width,Height);
  // 3. change size depending on theme. Not yet, because Parent=nil
  AutoSize:=true;
  // 4. changing size because of AutoSize=true. Not yet, because Parent=nil
  Caption:='Ok';
  // 5. Set Parent. Now all the above takes place, but in a single action.
  Parent:=Form1;
end;

When a control has a Parent, then all properties take effect immediately. Without a Parent many properties do nothing more than store the value. And as soon as the Parent is set every property is applied. This is especially true for grand children:

GroupBox1:=TGroupBox.Create(Self);
with GroupBox1 do begin
  with TButton1.Create(Self) do begin
    AutoSize:=true;
    Caption:='Click me';
    Parent:=GroupBox1;
  end;
  Parent:=Form1;
end;
Form1.Show;

Autosizing starts only after every parent is set up and the form becomes visible.

Avoid early Handle creation

As soon as the Handle of a TWinControl is created, every change of a property changes the visual thing (called the widget). Even if a control is not visible, when it has a Handle, changes are still expensive.

Use SetBounds instead of Left, Top, Width, Height

Instead of

with Button1 do begin
  Left:=10;
  Top:=10;
  Width:=100;
  Height:=25;
end;

Use

with Button1 do begin
  SetBounds(10,10,100,25);
end;

Left, Top, Width, Height are calling SetBounds. And every change of position or size invokes recalculation of all sibling controls and maybe recursively the parent and/or the grandchild controls.

DisableAlign / EnableAlign

When positioning many controls, it is a good idea to disable the recalculation of all auto sizing, aligning, anchoring.

DisableAlign;
try
  ListBox1.Width:=ClientWidth div 3;
  ListBox2.Width:=ClientWidth div 3;
  ListBox3.Width:=ClientWidth div 3;
finally
  EnableAlign;
end;

Note: Every DisableAlign call needs an EnableAlign call. For example if you call DisableAlign two times, you must call EnableAlign twice as well.

For Delphians: This works recursively. That means DisableAlign stops aligning in all child and grandchild controls.

Creating a non-rectangular window or control

One can easily create non-rectangular windows or controls in Lazarus. For this, one can simply call TWinControl.SetShape, with the visible region as a parameter. Note that this will work for both windows and controls, as both TCustomForm and TCustomControl descend from TWinControl. One can also call the LCLIntf routine SetWindowRgn, which is completely equivalent to calling the SetShape method.

Using SetWindowRgn the code will be similar to this:

uses LCLIntf, LCLType;

procedure TForm1.FormCreate(Sender: TObject);
var
  MyRegion: HRGN;
begin
  MyRegion := CreateRectRgn(0, 0, 100, 100);
  SetWindowRgn(Handle, MyRegion, True);
  DeleteObject(MyRegion);
end;

An equivalent code, which uses a higher level TRegion object available in Lazarus 0.9.31+ (note that the previous way of doing things is still supported and will be in the future too), is:

uses Graphics;

procedure TForm1.FormCreate(Sender: TObject);
var
  MyRegion: TRegion;
begin
  MyRegion := TRegion.Create;
  try
    MyRegion.AddRectangle(0, 0, 100, 100);
    Self.SetShape(MyRegion);
  finally
    MyRegion.Free;
  end;
end;

The result of this operation in a window in macOS using the Qt widgetset can be seen here:

non rectangular window.png

Note that SetShape can also accept a TBitmap to describe the transparent region.

See also:

Documentation entries:

Limitations:

In Gtk2 a region can only be set after a window is realized. Calling SetWindowRgn in the OnShow event handler doesn't work, the only way seems to be to call it from a timer set with interval 1, for example. Enable the timer in Form.OnShow and disable it in it's OnTimer handler.

Simulating Mouse and Keyboard input

It is very easy to simulate mouse and keyboard input in the LCL, just use the routines from the unit LCLMessageGlue like all widgetset interfaces do. This unit has routines like:

unit LCLMessageGlue;

function LCLSendMouseMoveMsg(const Target: TControl; XPos, YPos: smallint;
  ShiftState: TShiftState = []): PtrInt;
function LCLSendMouseDownMsg(const Target: TControl; XPos, YPos: smallint;
  Button: TMouseButton; ShiftState: TShiftState = []): PtrInt;
function LCLSendMouseUpMsg(const Target: TControl; XPos, YPos: smallint;
  Button: TMouseButton; ShiftState: TShiftState = []): PtrInt;
function LCLSendMouseWheelMsg(const Target: TControl; XPos, YPos, WheelDelta: smallint;
  ShiftState: TShiftState = []): PtrInt;
function LCLSendCaptureChangedMsg(const Target: TControl): PtrInt;
function LCLSendSelectionChangedMsg(const Target: TControl): PtrInt;
function LCLSendClickedMsg(const Target: TControl): PtrInt;
function LCLSendMouseEnterMsg(const Target: TControl): PtrInt;
function LCLSendMouseLeaveMsg(const Target: TControl): PtrInt;
...
function LCLSendKeyDownEvent(const Target: TControl; var CharCode: word;
  KeyData: PtrInt; BeforeEvent, IsSysKey: boolean): PtrInt;
function LCLSendKeyUpEvent(const Target: TControl; var CharCode: word;
  KeyData: PtrInt; BeforeEvent, IsSysKey: boolean): PtrInt;

Showing the Virtual Keyboard in Smartphones/tablets

To show the virtual keyboard when a widget receives focus just add the csRequiresKeyboardInput ControlStyle:

constructor TMyTextEditor.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle + [csRequiresKeyboardInput];
...
end;

Virtual keyboard.png

Iterating through all child controls of a TWinControl

This is very easy, just use a loop to iterate over TWinControl.ControlCount and TWinControl.Controls[Index]. The index is zero based.

procedure TWinControl.WriteLayoutDebugReport(const Prefix: string);
var
  i: Integer;
begin
  inherited WriteLayoutDebugReport(Prefix);
  for i:=0 to ControlCount-1 do
    Controls[i].WriteLayoutDebugReport(Prefix+'  ');
end;

Responding only once to an event

Sometimes one wants an event handler to be called just once. Say, for example, that you want to do something only the first time a form is activated, once the form and all its controls are fully loaded (which might not be the case when OnCreate is fired).

The solution is easy: simply set the corresponding property to Nil at the beginning of the handler:

procedure TMyForm.FormActivate(const Sender: TObject);
begin
  OnActivate := Nil;
  {... rest of the code here ...}
end;

and the event handler won't be called again.