Sign in Join
Wiki copytemporary

LCL Tips: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Creating a GUI by code
22
33It is possible to create the [[Overview of Free Pascal and Lazarus|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:
44
55Main program file:
66
77``` pascal
88program codedgui;
99
1010{$MODE DELPHI}{$H+}
1111
1212uses
1313 Interfaces, Forms, StdCtrls,
1414 MainForm;
1515
1616var
1717 MyForm: TMyForm;
1818begin
1919 Application.Initialize;
2020 Application.CreateForm(TMyForm, MyForm);
2121 Application.Run;
2222end.
2323```
2424
2525And a unit containing a form:
2626
2727``` pascal
2828unit mainform;
2929
3030{$MODE DELPHI}{$H+}
3131
3232interface
3333
3434uses Forms, StdCtrls;
3535
3636type
3737 TMyForm = class(TForm)
3838 public
3939 MyButton: TButton;
4040 procedure ButtonClick(ASender: TObject);
4141 constructor Create(AOwner: TComponent); override;
4242 end;
4343
4444implementation
4545
4646procedure TMyForm.ButtonClick(ASender:TObject);
4747begin
4848 Close;
4949end;
5050
5151constructor TMyForm.Create(AOwner: TComponent);
5252begin
5353 inherited Create(AOwner);
5454 //Hint: FormCreate() is called BEFORE Create() !
5555 //so You can also put this code into FormCreate()
5656 //(This is not the case when creating components ..)
5757
5858 Position := poScreenCenter;
5959 Height := 400;
6060 Width := 400;
6161
6262 VertScrollBar.Visible := False;
6363 HorzScrollBar.Visible := False;
6464
6565 MyButton := TButton.Create(Self);
6666 with MyButton do
6767 begin
6868 Height := 30;
6969 Left := 100;
7070 Top := 100;
7171 Width := 100;
7272 Caption := 'Close';
7373 OnClick := ButtonClick;
7474 Parent := Self;
7575 end;
7676
7777 // Add other component creation and property setting code here
7878end;
7979
8080end.
8181```
8282
8383## Create controls manually without overhead
8484
8585### Set the Parent as last
8686
8787**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.
8888
8989``` pascal
9090with TButton.Create(Form1) do begin
9191 // 1. creating a button sets the default size
9292 // 2. change position. No side effects, because Parent=nil
9393 SetBounds(10,10,Width,Height);
9494 // 3. change size depending on theme. Not yet, because Parent=nil
9595 AutoSize:=true;
9696 // 4. changing size because of AutoSize=true. Not yet, because Parent=nil
9797 Caption:='Ok';
9898 // 5. Set Parent. Now all the above takes place, but in a single action.
9999 Parent:=Form1;
100100end;
101101```
102102
103103When 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:
104104
105105``` pascal
106106GroupBox1:=TGroupBox.Create(Self);
107107with GroupBox1 do begin
108108 with TButton1.Create(Self) do begin
109109 AutoSize:=true;
110110 Caption:='Click me';
111111 Parent:=GroupBox1;
112112 end;
113113 Parent:=Form1;
114114end;
115115Form1.Show;
116116```
117117
118118Autosizing starts only after every parent is set up and the form becomes visible.
119119
120120### Avoid early Handle creation
121121
122122As 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.
123123
124124### Use SetBounds instead of Left, Top, Width, Height
125125
126126Instead of
127127
128128``` pascal
129129with Button1 do begin
130130 Left:=10;
131131 Top:=10;
132132 Width:=100;
133133 Height:=25;
134134end;
135135```
136136
137137Use
138138
139139``` pascal
140140with Button1 do begin
141141 SetBounds(10,10,100,25);
142142end;
143143```
144144
145145Left, 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.
146146
147147## DisableAlign / EnableAlign
148148
149149When positioning many controls, it is a good idea to disable the recalculation of all auto sizing, aligning, anchoring.
150150
151151``` pascal
152152DisableAlign;
153153try
154154 ListBox1.Width:=ClientWidth div 3;
155155 ListBox2.Width:=ClientWidth div 3;
156156 ListBox3.Width:=ClientWidth div 3;
157157finally
158158 EnableAlign;
159159end;
160160```
161161
162162**Note**: Every DisableAlign call needs an EnableAlign call. For example if you call DisableAlign two times, you must call EnableAlign twice as well.
163163
164164**For Delphians**: This works recursively. That means DisableAlign stops aligning in all child and grandchild controls.
165165
166166## Creating a non-rectangular window or control
167167
168168One 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.
169169
170170Using SetWindowRgn the code will be similar to this:
171171
172172``` pascal
173173uses LCLIntf, LCLType;
174174
175175procedure TForm1.FormCreate(Sender: TObject);
176176var
177177 MyRegion: HRGN;
178178begin
179179 MyRegion := CreateRectRgn(0, 0, 100, 100);
180180 SetWindowRgn(Handle, MyRegion, True);
181181 DeleteObject(MyRegion);
182182end;
183183```
184184
185185An 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:
186186
187187``` pascal
188188uses Graphics;
189189
190190procedure TForm1.FormCreate(Sender: TObject);
191191var
192192 MyRegion: TRegion;
193193begin
194194 MyRegion := TRegion.Create;
195195 try
196196 MyRegion.AddRectangle(0, 0, 100, 100);
197197 Self.SetShape(MyRegion);
198198 finally
199199 MyRegion.Free;
200200 end;
201201end;
202202```
203203
204204The result of this operation in a window in macOS using the Qt widgetset can be seen here:
205205
206-<figure>
207-<img src="non_rectangular_window.png" title="non_rectangular_window.png" />
208-<figcaption>non_rectangular_window.png</figcaption>
209-</figure>
206+![](/wiki-images/7/72/Non_rectangular_window.png)
210207
211208Note that SetShape can also accept a TBitmap to describe the transparent region.
212209
213210See also:
214211
215212- [[LCL Internals|LCL_Internals#Shaped_Windows]]
216213
217214Documentation entries:
218215
219216- <http://lazarus-ccr.sourceforge.net/docs/lcl/lclintf/setwindowrgn.html>
220217- <http://lazarus-ccr.sourceforge.net/docs/lcl/controls/twincontrol.setshape.html>
221218
222219Limitations:
223220
224221In 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.
225222
226223## Simulating Mouse and Keyboard input
227224
228225It 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:
229226
230227``` pascal
231228unit LCLMessageGlue;
232229
233230function LCLSendMouseMoveMsg(const Target: TControl; XPos, YPos: smallint;
234231 ShiftState: TShiftState = []): PtrInt;
235232function LCLSendMouseDownMsg(const Target: TControl; XPos, YPos: smallint;
236233 Button: TMouseButton; ShiftState: TShiftState = []): PtrInt;
237234function LCLSendMouseUpMsg(const Target: TControl; XPos, YPos: smallint;
238235 Button: TMouseButton; ShiftState: TShiftState = []): PtrInt;
239236function LCLSendMouseWheelMsg(const Target: TControl; XPos, YPos, WheelDelta: smallint;
240237 ShiftState: TShiftState = []): PtrInt;
241238function LCLSendCaptureChangedMsg(const Target: TControl): PtrInt;
242239function LCLSendSelectionChangedMsg(const Target: TControl): PtrInt;
243240function LCLSendClickedMsg(const Target: TControl): PtrInt;
244241function LCLSendMouseEnterMsg(const Target: TControl): PtrInt;
245242function LCLSendMouseLeaveMsg(const Target: TControl): PtrInt;
246243...
247244function LCLSendKeyDownEvent(const Target: TControl; var CharCode: word;
248245 KeyData: PtrInt; BeforeEvent, IsSysKey: boolean): PtrInt;
249246function LCLSendKeyUpEvent(const Target: TControl; var CharCode: word;
250247 KeyData: PtrInt; BeforeEvent, IsSysKey: boolean): PtrInt;
251248```
252249
253250## Showing the Virtual Keyboard in Smartphones/tablets
254251
255252To show the virtual keyboard when a widget receives focus just add the csRequiresKeyboardInput ControlStyle:
256253
257254``` pascal
258255constructor TMyTextEditor.Create(AOwner : TComponent);
259256begin
260257 inherited Create(AOwner);
261258 ControlStyle := ControlStyle + [csRequiresKeyboardInput];
262259...
263260end;
264261```
265262
266-<figure>
267-<img src="Virtual_keyboard.png" title="Virtual_keyboard.png" />
268-<figcaption>Virtual_keyboard.png</figcaption>
269-</figure>
263+![](/wiki-images/2/29/Virtual_keyboard.png)
270264
271265## Iterating through all child controls of a TWinControl
272266
273267This is very easy, just use a loop to iterate over TWinControl.ControlCount and TWinControl.Controls\[Index\]. The index is zero based.
274268
275269``` pascal
276270procedure TWinControl.WriteLayoutDebugReport(const Prefix: string);
277271var
278272 i: Integer;
279273begin
280274 inherited WriteLayoutDebugReport(Prefix);
281275 for i:=0 to ControlCount-1 do
282276 Controls[i].WriteLayoutDebugReport(Prefix+' ');
283277end;
284278```
285279
286280## Responding only once to an event
287281
288282Sometimes 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).
289283
290284The solution is easy: simply set the corresponding property to Nil at the beginning of the handler:
291285
292286``` pascal
293287procedure TMyForm.FormActivate(const Sender: TObject);
294288begin
295289 OnActivate := Nil;
296290 {... rest of the code here ...}
297291end;
298292```
299293
300294and the event handler won't be called again.
301295
302296---
303297*From the Free Pascal wiki, page "LCL Tips", as it stood on 2020-07-31 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history