Sign in Join
Wiki copytemporary

LCL Internals: what changed

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

OldNewLine
11------------------------------------------------------------------------
22
33> **Note:** This document is work in progress. You can help by writing sections of this document. If you cannot find the information you are looking for in this document, please add your question to the discussion page. This will help us to write documentation at a level that is neither too simple nor too complicated, and fully cover the areas that people want to know about.
44
55------------------------------------------------------------------------
66
77## Internals of the LCL
88
99There is the LCL, and the "interface". The LCL is the part that is platform independent, and it resides in the lazarus/lcl/ directory. This directory contains mainly class definitions. Many of the different controls are actually implemented in the lazarus/lcl/include/ directory in the various .inc files. This is to find the implementation of a specific control, TCustomMemo for example, faster (which is in custommemo.inc). Every .inc starts with a line {%MainUnit ...} to define where it is included.
1010
1111Then there is the "interface" which lives in a subdirectory of the lazarus/lcl/interfaces/ directory. The gtk interface is in gtk/, win32 in win32/, etc. They all have a Interfaces unit, which is used by the lcl and creates the main interface object. Usually the main interface object is defined in XXint.pp (win32int.pp), and implemented in various inc files, XXobject.inc, for the interface specific methods, XXwinapi.inc for winapi implementation methods, XXlistsl.inc for implementation of the stringlist used by the TComboBox, TListBox, and other such controls, XXcallback.inc for handling of widget events and taking appropriate action to notify the LCL.
1212
1313Every control has a WidgetSetClass property which is of the 'mirror' class in the interfaces directory, for example: mirror of TCustomEdit is TWSCustomEdit, which methods are implemented by TWin32WSCustomEdit in win32wsstdctrls. This is the way the LCL communicates with the interface, and how it lets the interface do things.
1414
1515Communication of interface back to LCL is mostly done by sending messages, usually 'DeliverMessage' which calls TControl.Perform(\<message_id\>, wparam, lparam) with wparam and lparam being the extra info for the message.
1616
1717### Pie and RadialPie
1818
1919The LCLIntf unit contains two functions to draw pie-shapes:
2020
2121`function `[[doc:lcl/lclintf/pie.html|Pie]]`(DC: HDC; x1, y1, x2, y2, sx, sy, ex, ey: Integer): Boolean;`\
2222`function `[[doc:lcl/lclintf/radialpie.html|RadialPie]]`(DC: HDC; x1, y1, x2, y2, Angle1, Angle2: Integer): Boolean; `
2323
2424The Pie function uses a two points (sx,sy) and (ex,ey) to indicate start and end of the arc. RadialPie uses Angles to indicate start and end of the arc.
2525
2626Pie calls TWidgetSet.Pie and RadialPie calls TWidgetSet.RadialPie. The default implementation of TWidgetSet.Pie is to convert the parameters to angles and to call TWidgetSet.RadialPie. TWidgetSet.RadialPie creates an array of points for the arc and calls TWidgetSet.Polygon.
2727
2828The win32 widgetset overrides TWidgetSet.Pie to call the Windows Pie function directly.
2929
3030> **Note:** in older versions of Lazarus there existed RadialPie with angles which did the same as the current RadialPie and RadialPie, which does the same as our Pie. Those functions have been removed in Lazarus 0.9.21.
3131
3232### Adding a new unit to the LCL
3333
3434First add the new unit name into allclunits.pp.
3535
3636To make sure the unit is registered in the components palette see the files RegisterLCL.pas and pkgfileslcl.inc, they are located in lazarus/packager.
3737
3838## How to create a new Widgetset
3939
4040This is a step-by-step tutorial of developing a new [[Widgetset|widgetset]]. It is based on my experience creating the basics of the new qt4 interface.
4141
4242To start with, why would someone want to add an Widgetset? The answer is to be able to port existing lazarus software to more platforms, without modifying their code.
4343
4444Now, let´s write the widgetset. First of all, you need to have pascal bindings for the widget and know how to use it. Normally this is not hard. A few hours doing basic tutorials available on the internet should be enougth to get started. If the bindings do not exist already, you need to create them. If the tutorials are on another language, translate them to pascal and make them work.
4545
4646Now, for Qt I utilized Den Jean qt4 bindings for pascal, and created a very basic Qt program using them:
4747
4848``` pascal
4949program qttest;
5050
5151uses qt4;
5252
5353var
5454 App: QApplicationH;
5555 MainWindow: QMainWindowH;
5656begin
5757 App := QApplication_Create(@argc,argv);
5858
5959 MainWindow := QMainWindow_Create;
6060
6161 QWidget_show(MainWindow);
6262
6363 QApplication_Exec;
6464end.
6565```
6666
6767The above project compiles and creates a qt4 program. Now we will use its code to write a new widgetset. After we are done, the lazarus program below will compile fine into a qt4 software:
6868
6969``` pascal
7070program qttest;
7171
7272{$mode objfpc}{$H+}
7373
7474uses
7575 Interfaces, Classes, Forms,
7676 { Add your units here }
7777 qtform;
7878
7979begin
8080 Application.Initialize;
8181 Application.CreateForm(TForm1, Form1);
8282 Application.Run;
8383end.
8484```
8585
8686Where the form is maintained by Lazarus IDE and designed visually.
8787
8888The first thing to do on a new widgetset is add an empty skeleton for it. Very early development widgetsets, like qt and carbon, can serve as an skeleton.
8989
9090Looking at the files on the many widgets you can see the first file to be called by the lcl: Interfaces.pas This file just calls another called QtInt.pas or similar. QtInt.pas has the code for the TWidgetSet class, which we must implement. On an empty skeleton you can see that the class has various functions it must implement:
9191
9292``` pascal
9393TQtWidgetSet = Class(TWidgetSet)
9494 private
9595 App: QApplicationH;
9696 public
9797 {$I qtwinapih.inc}
9898 {$I qtlclintfh.inc}
9999 public
100100 // Application
101101 procedure AppInit(var ScreenInfo: TScreenInfo); override;
102102 procedure AppRun(const ALoop: TApplicationMainLoop); override;
103103 procedure AppWaitMessage; override;
104104 procedure AppProcessMessages; override;
105105 procedure AppTerminate; override;
106106 procedure AppMinimize; override;
107107 procedure AppBringToFront; override;
108108 public
109109 constructor Create;
110110 destructor Destroy; override;
111111 function DCGetPixel(CanvasHandle: HDC; X, Y: integer): TGraphicsColor; override;
112112 procedure DCSetPixel(CanvasHandle: HDC; X, Y: integer; AColor: TGraphicsColor); override;
113113 procedure DCRedraw(CanvasHandle: HDC); override;
114114 procedure SetDesigning(AComponent: TComponent); override;
115115
116116 function InitHintFont(HintFont: TObject): Boolean; override;
117117
118118 // create and destroy
119119 function CreateComponent(Sender : TObject): THandle; override; // deprecated
120120 function CreateTimer(Interval: integer; TimerFunc: TFNTimerProc): integer; override;
121121 function DestroyTimer(TimerHandle: integer): boolean; override;
122122 end;
123123```
124124
125125### How to implement a new windowed component
126126
127127Windowed components are all descendents from TWinControl. Those controls have a Handle and thus, should be created by the Widgetset. It's easy to add new windowed components to a widgetset.
128128
129129Let's say you want to add TQtWSCustomEdit to Qt Widgetset. To start with TCustomEdit is a descendent of TWinControl and is located on the StdCtrls unit.
130130
131131Now, go to QtWSStrCtrls unit and look for the declaration of TQtWSCustomEdit.
132132
133133``` pascal
134134
135135TQtWSCustomEdit = class(TWSCustomEdit)
136136 private
137137 protected
138138 public
139139 end;
140140```
141141
142142Add static methods that are declared on TWSCustomEdit and override them. The code should now look like this:
143143
144144``` pascal
145145
146146TQtWSCustomEdit = class(TWSCustomEdit)
147147 private
148148 protected
149149 public
150150 class function CreateHandle(const AWinControl: TWinControl;
151151 const AParams: TCreateParams): HWND; override;
152152 class procedure DestroyHandle(const AWinControl: TWinControl); override;
153153{ class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override;
154154 class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
155155
156156 class procedure SetCharCase(const ACustomEdit: TCustomEdit; NewCase: TEditCharCase); override;
157157 class procedure SetEchoMode(const ACustomEdit: TCustomEdit; NewMode: TEchoMode); override;
158158 class procedure SetMaxLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
159159 class procedure SetPasswordChar(const ACustomEdit: TCustomEdit; NewChar: char); override;
160160 class procedure SetReadOnly(const ACustomEdit: TCustomEdit; NewReadOnly: boolean); override;
161161 class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
162162 class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
163163
164164 class procedure GetPreferredSize(const AWinControl: TWinControl;
165165 var PreferredWidth, PreferredHeight: integer); override;}
166166 end;
167167```
168168
169169The commented part of the code are procedures you need to implement for TCustomEdit to be fully functional, but just CreateHandle and DestroyHandle should be enough for it to be show on the form and be editable, so it fits our needs in this article.
170170
171171Hit CTRL+SHIFT+C to code complete and the implement CreateHandle and DestroyHandle. In the case of Qt4 the code will be like this:
172172
173173``` pascal
174174{ TQtWSCustomEdit }
175175
176176class function TQtWSCustomEdit.CreateHandle(const AWinControl: TWinControl;
177177 const AParams: TCreateParams): HWND;
178178var
179179 Widget: QWidgetH;
180180 Str: WideString;
181181begin
182182 // Creates the widget
183183 WriteLn('Calling QTextDocument_create');
184184 Str := WideString((AWinControl as TCustomMemo).Lines.Text);
185185 Widget := QTextEdit_create(@Str, QWidgetH(AWinControl.Parent.Handle));
186186
187187 // Sets it's initial properties
188188 QWidget_setGeometry(Widget, AWinControl.Left, AWinControl.Top,
189189 AWinControl.Width, AWinControl.Height);
190190
191191 QWidget_show(Widget);
192192
193193 Result := THandle(Widget);
194194end;
195195
196196class procedure TQtWSCustomEdit.DestroyHandle(const AWinControl: TWinControl);
197197begin
198198 QTextEdit_destroy(QTextEditH(AWinControl.Handle));
199199end;
200200```
201201
202202Now uncomment the like "RegisterWSComponent(TCustomEdit, TQtWSCustomEdit);" on the bottom of the unit and that's it!
203203
204204You can now drop a TCustomEdit on the bottom of a form and expect it to work. :^)
205205
206206### Implementing TBitmap
207207
208208To implement TBitmap it is necessary to understand TRawImage and TLazIntfImage as explained here: [[Developing with Graphics|Developing with Graphics#Working with TLazIntfImage.2C TRawImage and TLazCanvas]]
209209
210210So, let's say you want to compile the following code:
211211
212212``` pascal
213213procedure TMyForm.HandleOnPaint(Sender: TObject);
214214var
215215 Bitmap: TBitmap;
216216begin
217217 Bitmap := TBitmap.Create;
218218 try
219219 Bitmap.LoadFromFile('myfile.bmp');
220220 Canvas.Draw(0, 0, Bitmap);
221221 finally
222222 Bitmap.Free;
223223 end;
224224end;
225225```
226226
227227Below is the order on which functions from the widgetset interface are called when executing that code:
228228
2292291 - BeginPaint
230230
231231This will be called only if the OnPaint event sends zero as the DC for the paint event
232232
2332332 - GetDC(0);
234234
235235Just create a device context.
236236
2372373 - TCDWidgetSet.RawImage_QueryDescription
238238
239239The default implementation of this routine is good for most widgetsets
240240
2412414 - TCDWidgetSet.RawImage_CreateBitmaps
242242
243243Here you need to create a native image object and load it from RawData.Data where the information is stored based on your description of the pixel format on item 2.
244244
2452455 - CreateCompatibleDC(0)
246246
247247This creates a temporary DC just to store the image, but at this point there is no information about the image so at this point this DC is really dummy
248248
2492496 - SelectObject
250250
251251With the image as the object to be selected and the DC just created above as target DC.
252252
2532537 - StretchMaskBlt
254254
255255Finally the drawing function! DestDC is the DC allocated on BeginPaint.
256256
2572578 - EndPaint
258258
259259Again, not always utilized.
260260
261261### TBitmap.LoadFromDevice for screenshot taking
262262
263263It is recomended that you first implement TBitmap before trying this step.
264264
265265On LCL you can use the following code takes a screenshot from the entire screen and paints it on the canvas:
266266
267267``` pascal
268268var
269269 ScreenDC: HDC;
270270 Bitmap: TBitmap;
271271begin
272272 Bitmap := TBitmap.Create;
273273 try
274274 ScreenDC := GetDC(0);
275275 Bitmap.LoadFromDevice(ScreenDC);
276276 ReleaseDC(0, ScreenDC);
277277 Canvas.Draw(0, 0, Bitmap);
278278 finally
279279 Bitmap.Free;
280280 end;
281281end;
282282```
283283
284284If you already implemented TBitmap, there are only 2 new functions to be implemented for LoadFromDevice: GetDeviceSize and GetRawImageFromDevice
285285
286286Below is a big trace, covering all widgetset functions being called on a OnPaint event that takes a screenshot and paints it on the screen. This trace was taken with Qt widgetset, and may have some imperfections. The Handle numbers should be used to check which object is being utilized on the functions.
287287
288288` [WinAPI BeginPaint] Result=-1220713544`\
289289` [WinAPI GetClientBounds]`\
290290` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0`
291291
292292\<syntaxhighlight lang="pascal\> //Enters on Paint event
293293
294294Bitmap := TBitmap.Create; try
295295
296296` ScreenDC := GetDC(0);`
297297
298-</syntaxhighlight>
299-
300298` [WinAPI GetDC] hWnd: 0 Result: -1220712920`
301299
302300``` pascal
303301 Bitmap.LoadFromDevice(ScreenDC);
304302```
305303
306304` [WinAPI GetDeviceSize]`\
307305` [WinAPI GetRawImageFromDevice] SrcDC: -1220712920 SrcWidth: 0 SrcHeight: 0`\
308306` [WinAPI CreateBitmapFromRawImage] Width:1024 Height:768 DataSize: 3145728 CreateMask: False Bitmap:-1220746696`\
309307` [WinAPI GetObject] GDIObj: -1220746696 Result=84 ObjectType=Image`
310308
311309``` pascal
312310 ReleaseDC(0, ScreenDC);
313311```
314312
315313` [WinAPI ReleaseDC] hWnd: 0 DC: -1220712920`
316314
317315``` pascal
318316 Canvas.Draw(0, 0, Bitmap);
319317```
320318
321319` [WinAPI CreateCompatibleDC] DC: 0`\
322320` [WinAPI GetDC] hWnd: 0 Result: -1220712920`\
323321` [WinAPI SelectObject] DC=-1220712920 GDIObj=-1220746696 Result=0 ObjectType=Image`\
324322` [WinAPI StretchMaskBlt] DestDC:-1220713544 SrcDC:-1220712920 Image:137185120 X:0 Y:0 W:1024 H:768 XSrc:0 YSrc:0 WSrc:1024 HSrc:768`
325323
326324``` pascal
327325finally
328326 Bitmap.Free;
329327end;
330328```
331329
332330` [WinAPI SelectObject] DC=-1220712920 GDIObj=0 Invalid GDI Object`\
333331` [WinAPI DeleteObject] GDIObject: -1220746696 Result=False ObjectType=Image`
334332
335333Now exited the OnPaint event
336334
337335` [WinAPI DeleteObject] GDIObject: 0`\
338336` [WinAPI DeleteObject] GDIObject: 0`\
339337` [WinAPI DeleteObject] GDIObject: 0`\
340338` [WinAPI DeleteObject] GDIObject: 0`\
341339` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: -152 NewY: -246`\
342340` [WinAPI DeleteObject] GDIObject: 0`\
343341` [WinAPI DeleteObject] GDIObject: 0`\
344342` [WinAPI DeleteObject] GDIObject: 0`\
345343` [WinAPI DeleteObject] GDIObject: 0`
346344
347345``` pascal
348346TWidgetSet.InitializeCriticalSection
349347TWidgetSet.EnterCriticalSection
350348```
351349
352350` [WinAPI SelectObject] DC=-1220713544 GDIObj=0 Invalid GDI Object`\
353351` [WinAPI MoveToEx] DC:-1220713544 X:0 Y:0`\
354352` [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746760 Result=-1220746856 ObjectType=Brush`\
355353` [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746856 Result=-1220746856 ObjectType=Brush`
356354
357355``` pascal
358356TWidgetSet.LeaveCriticalSection
359357```
360358
361359` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0`\
362360` [WinAPI EndPaint] Handle: -1220611768 PS.HDC: -1220713544`
363361
364362### Implementing drawing in the OnPaint event of a form or another control
365363
366364For drawing in the OnPaint event of a form, the event itself comes from the underlaying widgetset library. The LCL interface should handle this event and create an appropriate DC object on the event handler and then call LCLSendPaintMsg. Besides that one should also implement the corresponding drawing methods, such as Rectangle or ExtTextOut. Pen, Brush and Font related routines might also be useful.
367365
368366Here is the OnPaint event handler from the Cocoa widgetset which shows how it calls LCLSentPaintMsg:
369367
370368``` pascal
371369procedure TLCLCommonCallback.Draw(ControlContext: NSGraphicsContext;
372370 const bounds, dirty:NSRect);
373371var
374372 struct : TPaintStruct;
375373begin
376374 if not Assigned(Context) then Context:=TCocoaContext.Create;
377375
378376 Context.ctx:=ControlContext;
379377 if Context.InitDraw(Round(bounds.size.width), Round(bounds.size.height)) then
380378 begin
381379 FillChar(struct, SizeOf(TPaintStruct), 0);
382380 struct.hdc := HDC(Context);
383381 {$IFDEF VerboseWinAPI}
384382 DebugLn(Format('[TLCLCommonCallback.Draw] OnPaint event started context: %x', [HDC(context)]));
385383 {$ENDIF}
386384 LCLSendPaintMsg(Target, HDC(Context), @struct);
387385 {$IFDEF VerboseWinAPI}
388386 DebugLn('[TLCLCommonCallback.Draw] OnPaint event ended');
389387 {$ENDIF}
390388 end;
391389end;
392390```
393391
394392And a list of WinAPI routines which were called when running this event:
395393
396394 [TCocoaWidgetSet.GetDC] hWnd: 0 Result: 12400C0
397395 [TLCLCommonCallback.Draw] OnPaint event started context: 1240340
398396 TCocoaWidgetSet.CreatePenIndirect
399397 TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EC460
400398 TCocoaWidgetSet.SelectObject Result: 0
401399 TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EBF00
402400 TCocoaWidgetSet.SelectObject Result: 0
403401 [TCocoaWidgetSet.Rectangle] DC: 1240340 X1: 100 Y1: 100 X2: 200 Y2: 200
404402 TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EC4A0
405403 TCocoaWidgetSet.SelectObject Result: 0
406404 [TCocoaWidgetSet.GetTextExtentPoint] DC: 1240340 Str: Some text Count: 9
407405 [TCocoaWidgetSet.GetTextExtentPoint] Size: 65,17
408406 [TLCLCommonCallback.Draw] OnPaint event ended
409407
410408### Implementing TLabel
411409
412410Implementing TLabel is particularly hard, despite it being such a basic component, because it requires that almost all painting be implemented. TLabel is not a windowed control, instead it depends on paint messages to be drawn directly into the form canvas.
413411
414412Before trying to get TLabel working it is recomended to test if drawing functions such as Rectangle work inside a form's OnPaint event.
415413
416414Several WinAPI methods need to be implemented, particularly:
417415
418416**Device Context Methods**
419417
420418BeginPaint, GetDC, EndPaint, ReleaseDC, CreateCompatibleDC
421419
422420see [[Device Contexts and GDI objects in the LCL interfaces]]
423421
424422**GDI Objects Methods**
425423
426424SelectObject, DeleteObject, CreateFontIndirect, CreateFontIndirectEx
427425
428426**Miscelaneous functions**
429427
430428InvalidateRect, GetClientBounds, SetWindowOrgEx
431429
432430**Text drawing Methods**
433431
434432DrawText. Instead of implementing DrawText one can also use the default TWidgetSet.DrawText, like the Carbon and Cocoa widgetsets do. But in this case it is necessary that one implements at least GetTextMetrics, GetTextExtentPoint and ExtTextOut. Without GetTextMetrics a form with a label will crash because the autosize will not be able to calculate the appropriate size for the label.
435433
436434**Region functions to determine if the control is behind another**
437435
438436CombineRgn, CreateRectRgn, GetClipRGN, RectVisible
439437
440438Below is the order in which paint procedures are called on a form with only one TLabel, to better understand the painting sequence:
441439
4424401 - GetDC is called once on software startup with hWnd = 0
443441
4444422 - The form is shown
445443
4464443 - GetDC is called again (this wouldn't happen without the label). A few font related functions are called, as well as DrawText with CalcRect set to True to calculate the size of the label.
447445
4484464 - InvalidateRect is called on the form canvas
449447
4504485 - Control goes back to the operating system until a paint message comes from the widgetset
451449
4524506 - BeginPaint is called, and at this point code on OnPaint event of the form will be executed
453451
4544527 - DrawText is called again with CalcRect set to false
455453
4564548 - The Painting ends.
457455
458456### Implementing visibility for forms and controls and window state
459457
460458The code that controls visibility is split between visibility for forms, and for controls
461459
462460**Visibility for forms and window state**
463461
464462This part also controls the state of the window (minimized, maximized or normal). It is implemented as a copy of the Windows API function ShowWindow, so you must implemente the TMyWidgetset.ShowWindow on the file mywinapi.inc Don´t forget to also add a header to the file mywinapih.inc
465463
466464Below is code that implements this function on the Qt widgetset. It should be very easy to understand, copy and implement on your own widgetset. You can also take a look how Gtk implements this. On Windows, the Windows API is called directly, of course, so there is no code to look at.
467465
468466``` pascal
469467{------------------------------------------------------------------------------
470468 function ShowWindow(hWnd: HWND; nCmdShow: Integer): Boolean;
471469
472470 nCmdShow:
473471 SW_SHOWNORMAL, SW_MINIMIZE, SW_SHOWMAXIMIZED
474472------------------------------------------------------------------------------}
475473function TQtWidgetSet.ShowWindow(hWnd: HWND; nCmdShow: Integer): Boolean;
476474var
477475 Widget: QWidgetH;
478476begin
479477 {$ifdef VerboseQtWinAPI}
480478 WriteLn('WinAPI ShowWindow');
481479 {$endif}
482480
483481 Result := False;
484482
485483 Widget := QWidgetH(hWnd);
486484
487485// if Widget = nil then RaiseException('TQtWidgetSet.ShowWindow hWnd is nil');
488486
489487 case nCmdShow of
490488
491489 SW_SHOW: QWidget_setVisible(Widget, True);
492490
493491 SW_SHOWNORMAL: QWidget_showNormal(Widget);
494492
495493 SW_MINIMIZE: QWidget_setWindowState(Widget, QtWindowMinimized);
496494
497495 SW_SHOWMINIMIZED: QWidget_showMinimized(Widget);
498496
499497 SW_SHOWMAXIMIZED: QWidget_showMaximized(Widget);
500498
501499 SW_HIDE: QWidget_setVisible(Widget, False);
502500
503501 end;
504502
505503 Result := True;
506504end;
507505```
508506
509507**Visibility for controls**
510508
511509For controls inside a form you need to implement TMyWSWinControl.ShowHide class function that resides on the TMyWSWinControl class on the file mywscontrols.pp
512510
513511Remember that most controls are descendent from TWinControl, so implementing this function there will guarantee that the Visible property is implemented for all standard controls that have it. Below is a sample code for Qt widgetset.
514512
515513``` pascal
516514{------------------------------------------------------------------------------
517515 Method: TQtWSWinControl.ShowHide
518516 Params: AWinControl - the calling object
519517
520518 Returns: Nothing
521519
522520 Shows or hides a widget.
523521 ------------------------------------------------------------------------------}
524522class procedure TQtWSWinControl.ShowHide(const AWinControl: TWinControl);
525523begin
526524 if AWinControl = nil then exit;
527525
528526 if not AWinControl.HandleAllocated then exit;
529527
530528 if AWinControl.HandleObjectShouldBeVisible then
531529 QWidget_setVisible(TQtWidget(AWinControl.Handle).Widget, True)
532530 else QWidget_setVisible(TQtWidget(AWinControl.Handle).Widget, False);
533531end;
534532```
535533
536534### Implementing TStrings based Components
537535
538536Some components use a TStrings to store the information they display, such as: TCustomMemo, TCustomListBox and TCustomComboBox.
539537
540538To implement those it is not enougth to only implement their functions on the TQtCustomMemo class for example. One of the functions to implement will be called GetStrings, which looks like this:
541539
542540``` pascal
543541class function TQtWSCustomListBox.GetStrings(const ACustomListBox: TCustomListBox): TStrings;
544542var
545543 ListWidgetH: QListWidgetH;
546544begin
547545 ListWidgetH := QListWidgetH((TQtWidget(ACustomListBox.Handle).Widget));
548546 Result := TQtListStrings.Create(ListWidgetH, ACustomListBox);
549547end;
550548```
551549
552550This function must return a TStrings descendent designed to detect when strings are added to or removed from the string list and to send this information to the widgetset to update the control. For example, the following declaration shows what TQtListStrings looks like:
553551
554552``` pascal
555553
556554TQtListStrings = class(TStrings)
557555 private
558556 FListChanged: Boolean; // StringList and QtListWidget out of sync
559557 FStringList: TStringList; // Holds the items to show
560558 FQtListWidget: QListWidgetH; // Qt Widget
561559 FOwner: TWinControl; // Lazarus Control Owning ListStrings
562560 FUpdating: Boolean; // We're changing Qt Widget
563561 procedure InternalUpdate;
564562 procedure ExternalUpdate(var Astr: TStringList; Clear: Boolean = True);
565563 procedure IsChanged; // OnChange triggered by program action
566564 protected
567565 function GetTextStr: string; override;
568566 function GetCount: integer; override;
569567 function Get(Index : Integer) : string; override;
570568 //procedure SetSorted(Val : boolean); virtual;
571569 public
572570 constructor Create(ListWidgetH : QListWidgetH; TheOwner: TWinControl);
573571 destructor Destroy; override;
574572 procedure Assign(Source : TPersistent); override;
575573 procedure Clear; override;
576574 procedure Delete(Index : integer); override;
577575 procedure Insert(Index : integer; const S: string); override;
578576 procedure SetText(TheText: PChar); override;
579577 //procedure Sort; virtual;
580578 public
581579 //property Sorted: boolean read FSorted write SetSorted;
582580 property Owner: TWinControl read FOwner;
583581 function ListChangedHandler(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
584582 end;
585583```
586584
587585You can see its implementation in the qtobjects.pas unit of the qt interface
588586
589587### Implementing Menus
590588
591589Menus are available on the LCL to create main menus or popup menus. A TMenu is the owner of a larger menu structure with many items. Items can have subitems, and don't need extra TMenus.
592590
593591Also remember that on LCL the handle is only created when needed and at that time all properties of the controls are already initialized. This helps a lot on widgetsets where depending on the properties of a menu item it can be of one class or another, like Qt.
594592
595593The following things need to be implemented in order for the menus to work:
596594
5975951\) All methods on the QtWSMenus unit, which will implement menu creation and modification
598596
5995972\) function TWinCEWidgetSet.SetMenu(AWindowHandle: HWND; AMenuHandle: HMENU): Boolean; from the wincewinapi.inc file, which will implement support for a main menu associated with a window.
600598
601599#### Menu Creation Order
602600
603601One important thing to understand when implementing menus, is in which order they are created. For example, we want to create the following menu structure:
604602
605-<figure>
606-<img src="Menu_creation_order.png" title="Menu_creation_order.png" />
607-<figcaption>Menu_creation_order.png</figcaption>
608-</figure>
603+![](/wiki-images/6/69/Menu_creation_order.png)
609604
610605And when our application is executed, there will be a 'Creating MenuItem' message with the caption of the menu each time TQtWSMenuItem.CreateHandle is called, and a 'Creating Menu' message with the name of the menu (TMenu descendents don't have a caption), each time TQtWSMenu.CreateHandle is called.
611606
612607Here is the resulting output of such software:
613608
614609 Creating Menu. Name: MainMenu1
615610 Creating MenuItem: Item1 Parent=Menu.Items : TMenuItem
616611 Creating MenuItem: SubItem11 Parent=Item1 : TMenuItem
617612 Creating MenuItem: SubItem12 Parent=Item1 : TMenuItem
618613 Creating MenuItem: SubItem13 Parent=Item1 : TMenuItem
619614 Creating MenuItem: SubItem14 Parent=Item1 : TMenuItem
620615 Creating MenuItem: SubSubItem141 Parent=SubItem14 : TMenuItem
621616 Creating MenuItem: SubSubItem142 Parent=SubItem14 : TMenuItem
622617 Creating MenuItem: SubSubItem143 Parent=SubItem14 : TMenuItem
623618 Creating MenuItem: SubSubItem144 Parent=SubItem14 : TMenuItem
624619 Creating MenuItem: Item2 Parent=Menu.Items : TMenuItem
625620 Creating MenuItem: SubItem21 Parent=Item2 : TMenuItem
626621 Creating MenuItem: SubItem22 Parent=Item2 : TMenuItem
627622 Creating MenuItem: SubItem23 Parent=Item2 : TMenuItem
628623 Creating MenuItem: Item3 Parent=Menu.Items : TMenuItem
629624 Creating MenuItem: Item4 Parent=Menu.Items : TMenuItem
630625
631626For all MenuItems one can use GetParentMenu to get their parent owner: Menu (TMainMenu).
632627
633628### Control enabling/disabling
634629
635630The current way to set control enabling/disabling is by implementing the winapi EnableWindow. This API should work generically on any control. It should enable/disable mouse and keyboard input for the specified window or control, but also mark it as uneditable by the user, by making it greyed for example.
636631
637632``` pascal
638633{------------------------------------------------------------------------------
639634 Method: EnableWindow
640635 Params: HWnd - handle to window
641636 BEnable - whether to enable the window
642637 Returns: If the window was previously disabled
643638
644639 Enables or disables mouse and keyboard input to the specified window or
645640 control.
646641 ------------------------------------------------------------------------------}
647642function TWin32WidgetSet.EnableWindow(hWnd: HWND; bEnable: Boolean): Boolean;
648643```
649644
650645### Return Control Text (Captions)
651646
652647The method to return control text is GetText().
653648
654649However, the method doesn't always have to return text (even if it can). It should only return text (by assigning AText value and setting result to TRUE), if a user can modify the text on the control itself (typically by typing it in by keyboard or some other manipulation).
655650
656651If the text itself is considered to be "static" then method can return FALSE at all times.
657652
658653LCL caches the text most of the time, and thus if control returns false, it would pull the cached value.
659654
660655``` pascal
661656{------------------------------------------------------------------------------
662657 Function: TWSWinControl.GetText
663658 Params: Sender: The control to retrieve the text from
664659 Returns: the requested text
665660
666661 Retrieves the text from a control.
667662 ------------------------------------------------------------------------------}
668663class function TWSWinControl.GetText(const AWinControl: TWinControl; var AText: String): Boolean;
669664```
670665
671666### Shaped Windows
672667
673668Windows can be shaped based on a TBitmap or on a TRegion. The region is the visible part.
674669
675670To implement shaped windows based on a TBitmap, implement TWSWinControl.setShape
676671
677672To implement shaped windows based on a TRegion implement LCLIntf.SetWindowRgn
678673
679674See also: [[LCL Tips|LCL Tips#Creating a non-rectangular window or control]]
680675
681676### System colors
682677
683678Some color constants are actually system colors, like clBtnFace, clForm, clWindow, etc, etc.
684679
685680To implement support for system colors the WinAPI routine GetSysColor should be implemented:
686681
687682`function GetSysColor(nIndex: Integer): DWORD; override;`
688683
689684And here is a snip of the color constants which should be supported. Check LCLType for the latest values:
690685
691686``` pascal
692687//==============================================
693688// API system Color constants pbd
694689// note these are usually shown ORed with
695690// $80000000 as these would have interfered with
696691// other MS color enumerations
697692// GetSysColor and SetSysColor expects the values
698693// below
699694//==============================================
700695
701696type
702697 COLORREF = LongInt;
703698 TColorRef = COLORREF;
704699
705700const
706701 CLR_INVALID = TColorRef($FFFFFFFF);
707702
708703 COLOR_SCROLLBAR = 0;
709704 COLOR_BACKGROUND = 1;
710705 COLOR_ACTIVECAPTION = 2;
711706 COLOR_INACTIVECAPTION = 3;
712707 COLOR_MENU = 4;
713708 COLOR_WINDOW = 5;
714709 COLOR_WINDOWFRAME = 6;
715710 COLOR_MENUTEXT = 7;
716711 COLOR_WINDOWTEXT = 8;
717712 COLOR_CAPTIONTEXT = 9;
718713 COLOR_ACTIVEBORDER = 10;
719714 COLOR_INACTIVEBORDER = 11;
720715 COLOR_APPWORKSPACE = 12;
721716 COLOR_HIGHLIGHT = 13;
722717 COLOR_HIGHLIGHTTEXT = 14;
723718 COLOR_BTNFACE = 15;
724719 COLOR_BTNSHADOW = 16;
725720 COLOR_GRAYTEXT = 17;
726721 COLOR_BTNTEXT = 18;
727722 COLOR_INACTIVECAPTIONTEXT = 19;
728723 COLOR_BTNHIGHLIGHT = 20;
729724 COLOR_3DDKSHADOW = 21;
730725 COLOR_3DLIGHT = 22;
731726 COLOR_INFOTEXT = 23;
732727 COLOR_INFOBK = 24;
733728 // PBD: 25 is unassigned in all the docs I can find
734729 // if someone finds what this is supposed to be then fill it in
735730 // note defaults below, and cl[ColorConst] in graphics
736731 COLOR_HOTLIGHT = 26;
737732 COLOR_GRADIENTACTIVECAPTION = 27;
738733 COLOR_GRADIENTINACTIVECAPTION = 28;
739734 COLOR_MENUHILIGHT = 29;
740735 COLOR_MENUBAR = 30;
741736
742737 COLOR_FORM = 31;
743738
744739 COLOR_ENDCOLORS = COLOR_FORM;
745740
746741 COLOR_DESKTOP = COLOR_BACKGROUND;
747742 COLOR_3DFACE = COLOR_BTNFACE;
748743 COLOR_3DSHADOW = COLOR_BTNSHADOW;
749744 COLOR_3DHIGHLIGHT = COLOR_BTNHIGHLIGHT;
750745 COLOR_3DHILIGHT = COLOR_BTNHIGHLIGHT;
751746 COLOR_BTNHILIGHT = COLOR_BTNHIGHLIGHT;
752747
753748 MAX_SYS_COLORS = COLOR_ENDCOLORS;
754749 SYS_COLOR_BASE = TColorRef($80000000);
755750```
756751
757752### ShowMessage
758753
759754These standard dialogs are implemented purely in the LCL in the following places:
760755
761756- Class TPromptDialog file lcl/include/promptdialog.inc
762757
763758### SpinEdit
764759
765760Both TFloatSpinEdit and TSpinEdit are implemented in the class TWSFloatSpinEdit.
766761
767762### Clipboard
768763
769764Clipboard support is implemented in lclintf by implementing Windows API routines. The routines are:
770765
771766``` pascal
772767function ClipboardFormatToMimeType(FormatID: TClipboardFormat): string; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
773768function ClipboardGetData(ClipboardType: TClipboardType;
774769 FormatID: TClipboardFormat; Stream: TStream): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
775770// ! ClipboardGetFormats: List will be created. You must free it yourself with FreeMem(List) !
776771function ClipboardGetFormats(ClipboardType: TClipboardType;
777772 var Count: integer; var List: PClipboardFormat): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
778773function ClipboardGetOwnerShip(ClipboardType: TClipboardType;
779774 OnRequestProc: TClipboardRequestEvent; FormatCount: integer;
780775 Formats: PClipboardFormat): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
781776function ClipboardRegisterFormat(const AMimeType: string): TClipboardFormat; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
782777```
783778
784779Sequence of calls during program startup:
785780
786781``` pascal
787782TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/bmp Result=51027040
788783TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/delphi.bitmap Result=0
789784TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/xpm Result=0
790785TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/png Result=0
791786TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/jpeg Result=0
792787```
793788
794789Sequence of calls during "Caption := Clipboard.AsText":
795790
796791``` pascal
797792TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=text/plain Result=51026976
798793TCocoaWidgetSet.ClipboardGetData ClipboardType=clipboard FormatID: 51026976
799794```
800795
801796### TSpeedButton
802797
803798This control is a TGraphicControl descendent, so it has no handle and paints itself. To retain native look, it uses the function ThemeServices.DrawElement(Details.Element=teButton) to paint itself, so implement this function to have this control working. Naturally, lot's of other DC, WindowOrg, Text Drawing and Painting routines will have to be working, as explained for TLabel.
804799
805800### TRadioButton and TCheckButton
806801
807802**Messages:**
808803
809804Basically the Widgetset must do:
810805
811806- Send LM_CHANGE when a radio button is unchecked/checked (LCL will take care if OnClick will be called or not)
812807- Dont send LM_CHANGE when TWSCustomCheckBox.SetState is called (SetChecked)
813808
814809See also this bug report: <http://bugs.freepascal.org/view.php?id=13939>
815810
816811### FullScreen support
817812
818813FullScreen is implemented as a Windows state wsFullScreen, and as such is implemented in LCLIntf.ShowWindow where the constant SW_SHOWFULLSCREEN should be handled.
819814
820815## An example of how the LCL interfaces work
821816
822817Below is a simple example. Suppose you have developed a trayicon component. How would you implement it in the LCL to work correctly on the various different supported platforms?
823818
824819You would need to generate the following files:
825820
826821\trayicon.pas
827822
828823\wstrayicon.pas
829824
830825\gtk\gtkwstrayicon.pas
831826
832827\gtk\trayintf.pas
833828
834829\win32\win32wstrayicon.pas
835830
836831\win32\trayintf.pas
837832
838833Providing separate wsXXX.pas and \\\$(LCLWidgetType)\XXXintf.pas files avoids the need for ifdefs altogether. You will need to add as a unit path \$(LCLWidgetType) to the XXXintf.pas file in order initialize the correct trayintf.pas file, which in turn initializes the correct WS Tray class.
839834
840835Inside trayicon.pas you include wstrayicon. Derive your main class from an LCL class, and only use wstrayicon in its implementation. All LCL classes that communicate with a widgetset, must be derived from [[doc:/lcl/lclclasses/tlclcomponent.html|TLCLComponent]] declared in the [[doc:/lcl/lclclasses|LCLClasses]] unit.
841836
842837``` pascal
843838unit TrayIcon;
844839
845840interface
846841
847842type
848843 TTrayIcon = class(TLCLComponent)
849844 public
850845 procedure DoTray;
851846 end;
852847
853848implementation
854849
855850uses wstrayicon;
856851
857852procedure TTrayIcon.DoTray;
858853begin
859854 // Call wstrayicon
860855end;
861856
862857end.
863858```
864859
865860in trayintf you use gtkwstrayicon or win32trayicon depending on which trayintf file it is.
866861
867862in wstrayicon you create a class like so:
868863
869864``` pascal
870865unit WSTrayIcon;
871866
872867uses WSLCLClasses, Controls, TrayIcon; // and other things as well
873868
874869TWSTrayIconClass = class of TWSTrayIcon;
875870TWSTrayIcon = class(TWSWinControl);
876871public
877872 class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
878873virtual; // these must all be virtual and class procedures!!
879874 class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); virtual;
880875 ....
881876end;
882877...
883878
884879implementation
885880
886881procedure TWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
887882begin
888883 //do nothing
889884end;
890885
891886procedure TWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
892887begin
893888 //do nothing
894889end;
895890```
896891
897892now in gtkwstrayicon.pas do this:
898893
899894``` pascal
900895uses WSTrayIcon, WSLCLClasses, Controls, TrayIcon, gtk, gdk;
901896
902897TGtkWSTrayIcon = class(TWSTrayIcon);
903898private
904899 class function FindSystemTray(const ATrayIcon: TCustomTrayIcon):
905900TWindow; virtual;
906901public
907902 class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); override;
908903 class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
909904override;
910905 class function CreateHandle(const AWinControl: TWinControl; const
911906AParams: TCreateParams): HWND; override;
912907 ....
913908end;
914909...
915910
916911implementation
917912
918913procedure TGtkWSTrayIcon.CreateHandle(const AWinControl: TWinControl;
919914const AParams: TCreateParams): HWND;
920915var
921916WidgetInfo: PWidgetInfo;
922917begin
923918
924919 Result := gtk_plug_new;
925920 WidgetInfo := CreateWidgetInfo(AWinControl, Result); // it's something
926921like this anyway
927922 TGtkWSWincontrolClass(WidgetSetClass).SetCallbacks(AWinControl);
928923 // and more stuff
929924end;
930925
931926function TGtkWSTrayIcon.FindSystemTray(const ATrayIcon:
932927TCustomTrayIcon): TWindow;
933928begin
934929 // do something
935930end;
936931
937932procedure TGtkWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
938933var
939934SystemTray: TWindow;
940935begin
941936 SystemTray := FindSystemTray(ATrayIcon);
942937 //do something
943938end;
944939
945940procedure TGtkWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
946941begin
947942 //do something
948943end;
949944
950945......
951946
952947initialization
953948
954949RegisterWSComponent(TCustomTrayIcon, TGtkWSTrayIcon); //this is very
955950important!!!
956951
957952end.
958953```
959954
960955then finally in trayicon.pas you go as normal
961956
962957``` pascal
963958uses WSTrayIcon; //etc. you DON'T include GtkWSTrayIcon here!
964959
965960TCustomTrayIcon = class(TWinControl)
966961public
967962 procedure EmbedControl;
968963....
969964end;
970965
971966...
972967procedure TTrayIcon.EmbedControl;
973968begin
974969 TWSTrayIconClass(WidgetSetClass).EmbedControl(Self);
975970
976971end;
977972```
978973
979974## Minimum Toolkit versions
980975
981976| Lazarus version | Min. FPC | Min. Gtk 2 | Min. Qt 4 | Min. Windows | Min. Windows CE | Min. macOS (Carbon) | Min. macOS (Cocoa) | Min. Req. of LCL-CustomDrawn |
982977|----|----|----|----|----|----|----|----|----|
983978| 0.9.24 | 2.2.0 | 2.6+ | 4.2+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
984979| 0.9.26 | 2.2.2 | 2.6+ | 4.3+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
985980| 0.9.28 | 2.2.4 | 2.8+ | 4.5+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
986981| 0.9.30 | 2.4.0 | 2.8+ | 4.5+ | Windows 98+ | Recommended 5.0+ | 10.4 | N/A | N/A |
987982| 0.9.31 | 2.4.4 | 2.8+ | 4.5+ | Windows 98+ | Recommended 5.0+ | 10.4 | 10.6 | Android 2.2+, Windows 2000+, X11, Snow Leopard 10.6+ |
988983| 1.2.6 | 2.6.4 | 2.8+ | 4.5+* | Windows 98+ | Recommended 5.0+ | 10.4 | 10.6 | Android 2.2+, Windows 2000+, X11, Snow Leopard 10.6+ |
989984
990985- \- Actually 4.5.0 ...4.8.4 with pre-built Qt4Pas.dll, 4.8.5+ requires a different Qt4Pas.dll
991986
992987See Also: [[Installing Lazarus on macOS|FPC + Lazarus Compatibility Matrix]].
993988
994989---
995990*From the Free Pascal wiki, page "LCL Internals", as it stood on 2021-12-09 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