Sign in Join
Wiki copytemporary

LCL Internals: what changed

Revision 2 to 3. 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
21-`function `[[doc:lcl/lclintf/pie.html|Pie]]`(DC: HDC; x1, y1, x2, y2, sx, sy, ex, ey: Integer): Boolean;`\
22-`function `[[doc:lcl/lclintf/radialpie.html|RadialPie]]`(DC: HDC; x1, y1, x2, y2, Angle1, Angle2: Integer): Boolean; `
21+``` pascal
22+function Pie(DC: HDC; x1, y1, x2, y2, sx, sy, ex, ey: Integer): Boolean;
23+function RadialPie(DC: HDC; x1, y1, x2, y2, Angle1, Angle2: Integer): Boolean;
24+```
2325
2426The 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.
2527
2628Pie 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.
2729
2830The win32 widgetset overrides TWidgetSet.Pie to call the Windows Pie function directly.
2931
3032> **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.
3133
3234### Adding a new unit to the LCL
3335
3436First add the new unit name into allclunits.pp.
3537
3638To make sure the unit is registered in the components palette see the files RegisterLCL.pas and pkgfileslcl.inc, they are located in lazarus/packager.
3739
3840## How to create a new Widgetset
3941
4042This 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.
4143
4244To 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.
4345
4446Now, 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.
4547
4648Now, for Qt I utilized Den Jean qt4 bindings for pascal, and created a very basic Qt program using them:
4749
4850``` pascal
4951program qttest;
5052
5153uses qt4;
5254
5355var
5456 App: QApplicationH;
5557 MainWindow: QMainWindowH;
5658begin
5759 App := QApplication_Create(@argc,argv);
5860
5961 MainWindow := QMainWindow_Create;
6062
6163 QWidget_show(MainWindow);
6264
6365 QApplication_Exec;
6466end.
6567```
6668
6769The 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:
6870
6971``` pascal
7072program qttest;
7173
7274{$mode objfpc}{$H+}
7375
7476uses
7577 Interfaces, Classes, Forms,
7678 { Add your units here }
7779 qtform;
7880
7981begin
8082 Application.Initialize;
8183 Application.CreateForm(TForm1, Form1);
8284 Application.Run;
8385end.
8486```
8587
8688Where the form is maintained by Lazarus IDE and designed visually.
8789
8890The 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.
8991
9092Looking 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:
9193
9294``` pascal
9395TQtWidgetSet = Class(TWidgetSet)
9496 private
9597 App: QApplicationH;
9698 public
9799 {$I qtwinapih.inc}
98100 {$I qtlclintfh.inc}
99101 public
100102 // Application
101103 procedure AppInit(var ScreenInfo: TScreenInfo); override;
102104 procedure AppRun(const ALoop: TApplicationMainLoop); override;
103105 procedure AppWaitMessage; override;
104106 procedure AppProcessMessages; override;
105107 procedure AppTerminate; override;
106108 procedure AppMinimize; override;
107109 procedure AppBringToFront; override;
108110 public
109111 constructor Create;
110112 destructor Destroy; override;
111113 function DCGetPixel(CanvasHandle: HDC; X, Y: integer): TGraphicsColor; override;
112114 procedure DCSetPixel(CanvasHandle: HDC; X, Y: integer; AColor: TGraphicsColor); override;
113115 procedure DCRedraw(CanvasHandle: HDC); override;
114116 procedure SetDesigning(AComponent: TComponent); override;
115117
116118 function InitHintFont(HintFont: TObject): Boolean; override;
117119
118120 // create and destroy
119121 function CreateComponent(Sender : TObject): THandle; override; // deprecated
120122 function CreateTimer(Interval: integer; TimerFunc: TFNTimerProc): integer; override;
121123 function DestroyTimer(TimerHandle: integer): boolean; override;
122124 end;
123125```
124126
125127### How to implement a new windowed component
126128
127129Windowed 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.
128130
129131Let'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.
130132
131133Now, go to QtWSStrCtrls unit and look for the declaration of TQtWSCustomEdit.
132134
133135``` pascal
134136
135137TQtWSCustomEdit = class(TWSCustomEdit)
136138 private
137139 protected
138140 public
139141 end;
140142```
141143
142144Add static methods that are declared on TWSCustomEdit and override them. The code should now look like this:
143145
144146``` pascal
145147
146148TQtWSCustomEdit = class(TWSCustomEdit)
147149 private
148150 protected
149151 public
150152 class function CreateHandle(const AWinControl: TWinControl;
151153 const AParams: TCreateParams): HWND; override;
152154 class procedure DestroyHandle(const AWinControl: TWinControl); override;
153155{ class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override;
154156 class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
155157
156158 class procedure SetCharCase(const ACustomEdit: TCustomEdit; NewCase: TEditCharCase); override;
157159 class procedure SetEchoMode(const ACustomEdit: TCustomEdit; NewMode: TEchoMode); override;
158160 class procedure SetMaxLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
159161 class procedure SetPasswordChar(const ACustomEdit: TCustomEdit; NewChar: char); override;
160162 class procedure SetReadOnly(const ACustomEdit: TCustomEdit; NewReadOnly: boolean); override;
161163 class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
162164 class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
163165
164166 class procedure GetPreferredSize(const AWinControl: TWinControl;
165167 var PreferredWidth, PreferredHeight: integer); override;}
166168 end;
167169```
168170
169171The 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.
170172
171173Hit CTRL+SHIFT+C to code complete and the implement CreateHandle and DestroyHandle. In the case of Qt4 the code will be like this:
172174
173175``` pascal
174176{ TQtWSCustomEdit }
175177
176178class function TQtWSCustomEdit.CreateHandle(const AWinControl: TWinControl;
177179 const AParams: TCreateParams): HWND;
178180var
179181 Widget: QWidgetH;
180182 Str: WideString;
181183begin
182184 // Creates the widget
183185 WriteLn('Calling QTextDocument_create');
184186 Str := WideString((AWinControl as TCustomMemo).Lines.Text);
185187 Widget := QTextEdit_create(@Str, QWidgetH(AWinControl.Parent.Handle));
186188
187189 // Sets it's initial properties
188190 QWidget_setGeometry(Widget, AWinControl.Left, AWinControl.Top,
189191 AWinControl.Width, AWinControl.Height);
190192
191193 QWidget_show(Widget);
192194
193195 Result := THandle(Widget);
194196end;
195197
196198class procedure TQtWSCustomEdit.DestroyHandle(const AWinControl: TWinControl);
197199begin
198200 QTextEdit_destroy(QTextEditH(AWinControl.Handle));
199201end;
200202```
201203
202204Now uncomment the like "RegisterWSComponent(TCustomEdit, TQtWSCustomEdit);" on the bottom of the unit and that's it!
203205
204206You can now drop a TCustomEdit on the bottom of a form and expect it to work. :^)
205207
206208### Implementing TBitmap
207209
208210To 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]]
209211
210212So, let's say you want to compile the following code:
211213
212214``` pascal
213215procedure TMyForm.HandleOnPaint(Sender: TObject);
214216var
215217 Bitmap: TBitmap;
216218begin
217219 Bitmap := TBitmap.Create;
218220 try
219221 Bitmap.LoadFromFile('myfile.bmp');
220222 Canvas.Draw(0, 0, Bitmap);
221223 finally
222224 Bitmap.Free;
223225 end;
224226end;
225227```
226228
227229Below is the order on which functions from the widgetset interface are called when executing that code:
228230
2292311 - BeginPaint
230232
231233This will be called only if the OnPaint event sends zero as the DC for the paint event
232234
2332352 - GetDC(0);
234236
235237Just create a device context.
236238
2372393 - TCDWidgetSet.RawImage_QueryDescription
238240
239241The default implementation of this routine is good for most widgetsets
240242
2412434 - TCDWidgetSet.RawImage_CreateBitmaps
242244
243245Here 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.
244246
2452475 - CreateCompatibleDC(0)
246248
247249This 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
248250
2492516 - SelectObject
250252
251253With the image as the object to be selected and the DC just created above as target DC.
252254
2532557 - StretchMaskBlt
254256
255257Finally the drawing function! DestDC is the DC allocated on BeginPaint.
256258
2572598 - EndPaint
258260
259261Again, not always utilized.
260262
261263### TBitmap.LoadFromDevice for screenshot taking
262264
263265It is recomended that you first implement TBitmap before trying this step.
264266
265267On LCL you can use the following code takes a screenshot from the entire screen and paints it on the canvas:
266268
267269``` pascal
268270var
269271 ScreenDC: HDC;
270272 Bitmap: TBitmap;
271273begin
272274 Bitmap := TBitmap.Create;
273275 try
274276 ScreenDC := GetDC(0);
275277 Bitmap.LoadFromDevice(ScreenDC);
276278 ReleaseDC(0, ScreenDC);
277279 Canvas.Draw(0, 0, Bitmap);
278280 finally
279281 Bitmap.Free;
280282 end;
281283end;
282284```
283285
284286If you already implemented TBitmap, there are only 2 new functions to be implemented for LoadFromDevice: GetDeviceSize and GetRawImageFromDevice
285287
286288Below 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.
287289
288-` [WinAPI BeginPaint] Result=-1220713544`\
289-` [WinAPI GetClientBounds]`\
290-` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0`
290+```text
291+ [WinAPI BeginPaint] Result=-1220713544
292+ [WinAPI GetClientBounds]
293+ [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0
294+```
291295
292296\<syntaxhighlight lang="pascal\> //Enters on Paint event
293297
294298Bitmap := TBitmap.Create; try
295299
296300` ScreenDC := GetDC(0);`
297301
298-` [WinAPI GetDC] hWnd: 0 Result: -1220712920`
302+```text
303+ [WinAPI GetDC] hWnd: 0 Result: -1220712920
304+```
299305
300306``` pascal
301307 Bitmap.LoadFromDevice(ScreenDC);
302308```
303309
304-` [WinAPI GetDeviceSize]`\
305-` [WinAPI GetRawImageFromDevice] SrcDC: -1220712920 SrcWidth: 0 SrcHeight: 0`\
306-` [WinAPI CreateBitmapFromRawImage] Width:1024 Height:768 DataSize: 3145728 CreateMask: False Bitmap:-1220746696`\
307-` [WinAPI GetObject] GDIObj: -1220746696 Result=84 ObjectType=Image`
310+```text
311+ [WinAPI GetDeviceSize]
312+ [WinAPI GetRawImageFromDevice] SrcDC: -1220712920 SrcWidth: 0 SrcHeight: 0
313+ [WinAPI CreateBitmapFromRawImage] Width:1024 Height:768 DataSize: 3145728 CreateMask: False Bitmap:-1220746696
314+ [WinAPI GetObject] GDIObj: -1220746696 Result=84 ObjectType=Image
315+```
308316
309317``` pascal
310318 ReleaseDC(0, ScreenDC);
311319```
312320
313-` [WinAPI ReleaseDC] hWnd: 0 DC: -1220712920`
321+```text
322+ [WinAPI ReleaseDC] hWnd: 0 DC: -1220712920
323+```
314324
315325``` pascal
316326 Canvas.Draw(0, 0, Bitmap);
317327```
318328
319-` [WinAPI CreateCompatibleDC] DC: 0`\
320-` [WinAPI GetDC] hWnd: 0 Result: -1220712920`\
321-` [WinAPI SelectObject] DC=-1220712920 GDIObj=-1220746696 Result=0 ObjectType=Image`\
322-` [WinAPI StretchMaskBlt] DestDC:-1220713544 SrcDC:-1220712920 Image:137185120 X:0 Y:0 W:1024 H:768 XSrc:0 YSrc:0 WSrc:1024 HSrc:768`
329+```text
330+ [WinAPI CreateCompatibleDC] DC: 0
331+ [WinAPI GetDC] hWnd: 0 Result: -1220712920
332+ [WinAPI SelectObject] DC=-1220712920 GDIObj=-1220746696 Result=0 ObjectType=Image
333+ [WinAPI StretchMaskBlt] DestDC:-1220713544 SrcDC:-1220712920 Image:137185120 X:0 Y:0 W:1024 H:768 XSrc:0 YSrc:0 WSrc:1024 HSrc:768
334+```
323335
324336``` pascal
325337finally
326338 Bitmap.Free;
327339end;
328340```
329341
330-` [WinAPI SelectObject] DC=-1220712920 GDIObj=0 Invalid GDI Object`\
331-` [WinAPI DeleteObject] GDIObject: -1220746696 Result=False ObjectType=Image`
342+``` pascal
343+ [WinAPI SelectObject] DC=-1220712920 GDIObj=0 Invalid GDI Object
344+ [WinAPI DeleteObject] GDIObject: -1220746696 Result=False ObjectType=Image
345+```
332346
333347Now exited the OnPaint event
334348
335-` [WinAPI DeleteObject] GDIObject: 0`\
336-` [WinAPI DeleteObject] GDIObject: 0`\
337-` [WinAPI DeleteObject] GDIObject: 0`\
338-` [WinAPI DeleteObject] GDIObject: 0`\
339-` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: -152 NewY: -246`\
340-` [WinAPI DeleteObject] GDIObject: 0`\
341-` [WinAPI DeleteObject] GDIObject: 0`\
342-` [WinAPI DeleteObject] GDIObject: 0`\
343-` [WinAPI DeleteObject] GDIObject: 0`
349+```text
350+ [WinAPI DeleteObject] GDIObject: 0
351+ [WinAPI DeleteObject] GDIObject: 0
352+ [WinAPI DeleteObject] GDIObject: 0
353+ [WinAPI DeleteObject] GDIObject: 0
354+ [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: -152 NewY: -246
355+ [WinAPI DeleteObject] GDIObject: 0
356+ [WinAPI DeleteObject] GDIObject: 0
357+ [WinAPI DeleteObject] GDIObject: 0
358+ [WinAPI DeleteObject] GDIObject: 0
359+```
344360
345361``` pascal
346362TWidgetSet.InitializeCriticalSection
347363TWidgetSet.EnterCriticalSection
348364```
349365
350-` [WinAPI SelectObject] DC=-1220713544 GDIObj=0 Invalid GDI Object`\
351-` [WinAPI MoveToEx] DC:-1220713544 X:0 Y:0`\
352-` [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746760 Result=-1220746856 ObjectType=Brush`\
353-` [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746856 Result=-1220746856 ObjectType=Brush`
366+``` pascal
367+ [WinAPI SelectObject] DC=-1220713544 GDIObj=0 Invalid GDI Object
368+ [WinAPI MoveToEx] DC:-1220713544 X:0 Y:0
369+ [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746760 Result=-1220746856 ObjectType=Brush
370+ [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746856 Result=-1220746856 ObjectType=Brush
371+```
354372
355373``` pascal
356374TWidgetSet.LeaveCriticalSection
357375```
358376
359-` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0`\
360-` [WinAPI EndPaint] Handle: -1220611768 PS.HDC: -1220713544`
377+```text
378+ [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0
379+ [WinAPI EndPaint] Handle: -1220611768 PS.HDC: -1220713544
380+```
361381
362382### Implementing drawing in the OnPaint event of a form or another control
363383
364384For 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.
365385
366386Here is the OnPaint event handler from the Cocoa widgetset which shows how it calls LCLSentPaintMsg:
367387
368388``` pascal
369389procedure TLCLCommonCallback.Draw(ControlContext: NSGraphicsContext;
370390 const bounds, dirty:NSRect);
371391var
372392 struct : TPaintStruct;
373393begin
374394 if not Assigned(Context) then Context:=TCocoaContext.Create;
375395
376396 Context.ctx:=ControlContext;
377397 if Context.InitDraw(Round(bounds.size.width), Round(bounds.size.height)) then
378398 begin
379399 FillChar(struct, SizeOf(TPaintStruct), 0);
380400 struct.hdc := HDC(Context);
381401 {$IFDEF VerboseWinAPI}
382402 DebugLn(Format('[TLCLCommonCallback.Draw] OnPaint event started context: %x', [HDC(context)]));
383403 {$ENDIF}
384404 LCLSendPaintMsg(Target, HDC(Context), @struct);
385405 {$IFDEF VerboseWinAPI}
386406 DebugLn('[TLCLCommonCallback.Draw] OnPaint event ended');
387407 {$ENDIF}
388408 end;
389409end;
390410```
391411
392412And a list of WinAPI routines which were called when running this event:
393413
394414 [TCocoaWidgetSet.GetDC] hWnd: 0 Result: 12400C0
395415 [TLCLCommonCallback.Draw] OnPaint event started context: 1240340
396416 TCocoaWidgetSet.CreatePenIndirect
397417 TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EC460
398418 TCocoaWidgetSet.SelectObject Result: 0
399419 TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EBF00
400420 TCocoaWidgetSet.SelectObject Result: 0
401421 [TCocoaWidgetSet.Rectangle] DC: 1240340 X1: 100 Y1: 100 X2: 200 Y2: 200
402422 TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EC4A0
403423 TCocoaWidgetSet.SelectObject Result: 0
404424 [TCocoaWidgetSet.GetTextExtentPoint] DC: 1240340 Str: Some text Count: 9
405425 [TCocoaWidgetSet.GetTextExtentPoint] Size: 65,17
406426 [TLCLCommonCallback.Draw] OnPaint event ended
407427
408428### Implementing TLabel
409429
410430Implementing 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.
411431
412432Before trying to get TLabel working it is recomended to test if drawing functions such as Rectangle work inside a form's OnPaint event.
413433
414434Several WinAPI methods need to be implemented, particularly:
415435
416436**Device Context Methods**
417437
418438BeginPaint, GetDC, EndPaint, ReleaseDC, CreateCompatibleDC
419439
420440see [[Device Contexts and GDI objects in the LCL interfaces]]
421441
422442**GDI Objects Methods**
423443
424444SelectObject, DeleteObject, CreateFontIndirect, CreateFontIndirectEx
425445
426446**Miscelaneous functions**
427447
428448InvalidateRect, GetClientBounds, SetWindowOrgEx
429449
430450**Text drawing Methods**
431451
432452DrawText. 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.
433453
434454**Region functions to determine if the control is behind another**
435455
436456CombineRgn, CreateRectRgn, GetClipRGN, RectVisible
437457
438458Below is the order in which paint procedures are called on a form with only one TLabel, to better understand the painting sequence:
439459
4404601 - GetDC is called once on software startup with hWnd = 0
441461
4424622 - The form is shown
443463
4444643 - 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.
445465
4464664 - InvalidateRect is called on the form canvas
447467
4484685 - Control goes back to the operating system until a paint message comes from the widgetset
449469
4504706 - BeginPaint is called, and at this point code on OnPaint event of the form will be executed
451471
4524727 - DrawText is called again with CalcRect set to false
453473
4544748 - The Painting ends.
455475
456476### Implementing visibility for forms and controls and window state
457477
458478The code that controls visibility is split between visibility for forms, and for controls
459479
460480**Visibility for forms and window state**
461481
462482This 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
463483
464484Below 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.
465485
466486``` pascal
467487{------------------------------------------------------------------------------
468488 function ShowWindow(hWnd: HWND; nCmdShow: Integer): Boolean;
469489
470490 nCmdShow:
471491 SW_SHOWNORMAL, SW_MINIMIZE, SW_SHOWMAXIMIZED
472492------------------------------------------------------------------------------}
473493function TQtWidgetSet.ShowWindow(hWnd: HWND; nCmdShow: Integer): Boolean;
474494var
475495 Widget: QWidgetH;
476496begin
477497 {$ifdef VerboseQtWinAPI}
478498 WriteLn('WinAPI ShowWindow');
479499 {$endif}
480500
481501 Result := False;
482502
483503 Widget := QWidgetH(hWnd);
484504
485505// if Widget = nil then RaiseException('TQtWidgetSet.ShowWindow hWnd is nil');
486506
487507 case nCmdShow of
488508
489509 SW_SHOW: QWidget_setVisible(Widget, True);
490510
491511 SW_SHOWNORMAL: QWidget_showNormal(Widget);
492512
493513 SW_MINIMIZE: QWidget_setWindowState(Widget, QtWindowMinimized);
494514
495515 SW_SHOWMINIMIZED: QWidget_showMinimized(Widget);
496516
497517 SW_SHOWMAXIMIZED: QWidget_showMaximized(Widget);
498518
499519 SW_HIDE: QWidget_setVisible(Widget, False);
500520
501521 end;
502522
503523 Result := True;
504524end;
505525```
506526
507527**Visibility for controls**
508528
509529For controls inside a form you need to implement TMyWSWinControl.ShowHide class function that resides on the TMyWSWinControl class on the file mywscontrols.pp
510530
511531Remember 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.
512532
513533``` pascal
514534{------------------------------------------------------------------------------
515535 Method: TQtWSWinControl.ShowHide
516536 Params: AWinControl - the calling object
517537
518538 Returns: Nothing
519539
520540 Shows or hides a widget.
521541 ------------------------------------------------------------------------------}
522542class procedure TQtWSWinControl.ShowHide(const AWinControl: TWinControl);
523543begin
524544 if AWinControl = nil then exit;
525545
526546 if not AWinControl.HandleAllocated then exit;
527547
528548 if AWinControl.HandleObjectShouldBeVisible then
529549 QWidget_setVisible(TQtWidget(AWinControl.Handle).Widget, True)
530550 else QWidget_setVisible(TQtWidget(AWinControl.Handle).Widget, False);
531551end;
532552```
533553
534554### Implementing TStrings based Components
535555
536556Some components use a TStrings to store the information they display, such as: TCustomMemo, TCustomListBox and TCustomComboBox.
537557
538558To 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:
539559
540560``` pascal
541561class function TQtWSCustomListBox.GetStrings(const ACustomListBox: TCustomListBox): TStrings;
542562var
543563 ListWidgetH: QListWidgetH;
544564begin
545565 ListWidgetH := QListWidgetH((TQtWidget(ACustomListBox.Handle).Widget));
546566 Result := TQtListStrings.Create(ListWidgetH, ACustomListBox);
547567end;
548568```
549569
550570This 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:
551571
552572``` pascal
553573
554574TQtListStrings = class(TStrings)
555575 private
556576 FListChanged: Boolean; // StringList and QtListWidget out of sync
557577 FStringList: TStringList; // Holds the items to show
558578 FQtListWidget: QListWidgetH; // Qt Widget
559579 FOwner: TWinControl; // Lazarus Control Owning ListStrings
560580 FUpdating: Boolean; // We're changing Qt Widget
561581 procedure InternalUpdate;
562582 procedure ExternalUpdate(var Astr: TStringList; Clear: Boolean = True);
563583 procedure IsChanged; // OnChange triggered by program action
564584 protected
565585 function GetTextStr: string; override;
566586 function GetCount: integer; override;
567587 function Get(Index : Integer) : string; override;
568588 //procedure SetSorted(Val : boolean); virtual;
569589 public
570590 constructor Create(ListWidgetH : QListWidgetH; TheOwner: TWinControl);
571591 destructor Destroy; override;
572592 procedure Assign(Source : TPersistent); override;
573593 procedure Clear; override;
574594 procedure Delete(Index : integer); override;
575595 procedure Insert(Index : integer; const S: string); override;
576596 procedure SetText(TheText: PChar); override;
577597 //procedure Sort; virtual;
578598 public
579599 //property Sorted: boolean read FSorted write SetSorted;
580600 property Owner: TWinControl read FOwner;
581601 function ListChangedHandler(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
582602 end;
583603```
584604
585605You can see its implementation in the qtobjects.pas unit of the qt interface
586606
587607### Implementing Menus
588608
589609Menus 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.
590610
591611Also 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.
592612
593613The following things need to be implemented in order for the menus to work:
594614
5956151\) All methods on the QtWSMenus unit, which will implement menu creation and modification
596616
5976172\) 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.
598618
599619#### Menu Creation Order
600620
601621One important thing to understand when implementing menus, is in which order they are created. For example, we want to create the following menu structure:
602622
603623![](/wiki-images/6/69/Menu_creation_order.png)
604624
605625And 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.
606626
607627Here is the resulting output of such software:
608628
609629 Creating Menu. Name: MainMenu1
610630 Creating MenuItem: Item1 Parent=Menu.Items : TMenuItem
611631 Creating MenuItem: SubItem11 Parent=Item1 : TMenuItem
612632 Creating MenuItem: SubItem12 Parent=Item1 : TMenuItem
613633 Creating MenuItem: SubItem13 Parent=Item1 : TMenuItem
614634 Creating MenuItem: SubItem14 Parent=Item1 : TMenuItem
615635 Creating MenuItem: SubSubItem141 Parent=SubItem14 : TMenuItem
616636 Creating MenuItem: SubSubItem142 Parent=SubItem14 : TMenuItem
617637 Creating MenuItem: SubSubItem143 Parent=SubItem14 : TMenuItem
618638 Creating MenuItem: SubSubItem144 Parent=SubItem14 : TMenuItem
619639 Creating MenuItem: Item2 Parent=Menu.Items : TMenuItem
620640 Creating MenuItem: SubItem21 Parent=Item2 : TMenuItem
621641 Creating MenuItem: SubItem22 Parent=Item2 : TMenuItem
622642 Creating MenuItem: SubItem23 Parent=Item2 : TMenuItem
623643 Creating MenuItem: Item3 Parent=Menu.Items : TMenuItem
624644 Creating MenuItem: Item4 Parent=Menu.Items : TMenuItem
625645
626646For all MenuItems one can use GetParentMenu to get their parent owner: Menu (TMainMenu).
627647
628648### Control enabling/disabling
629649
630650The 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.
631651
632652``` pascal
633653{------------------------------------------------------------------------------
634654 Method: EnableWindow
635655 Params: HWnd - handle to window
636656 BEnable - whether to enable the window
637657 Returns: If the window was previously disabled
638658
639659 Enables or disables mouse and keyboard input to the specified window or
640660 control.
641661 ------------------------------------------------------------------------------}
642662function TWin32WidgetSet.EnableWindow(hWnd: HWND; bEnable: Boolean): Boolean;
643663```
644664
645665### Return Control Text (Captions)
646666
647667The method to return control text is GetText().
648668
649669However, 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).
650670
651671If the text itself is considered to be "static" then method can return FALSE at all times.
652672
653673LCL caches the text most of the time, and thus if control returns false, it would pull the cached value.
654674
655675``` pascal
656676{------------------------------------------------------------------------------
657677 Function: TWSWinControl.GetText
658678 Params: Sender: The control to retrieve the text from
659679 Returns: the requested text
660680
661681 Retrieves the text from a control.
662682 ------------------------------------------------------------------------------}
663683class function TWSWinControl.GetText(const AWinControl: TWinControl; var AText: String): Boolean;
664684```
665685
666686### Shaped Windows
667687
668688Windows can be shaped based on a TBitmap or on a TRegion. The region is the visible part.
669689
670690To implement shaped windows based on a TBitmap, implement TWSWinControl.setShape
671691
672692To implement shaped windows based on a TRegion implement LCLIntf.SetWindowRgn
673693
674694See also: [[LCL Tips|LCL Tips#Creating a non-rectangular window or control]]
675695
676696### System colors
677697
678698Some color constants are actually system colors, like clBtnFace, clForm, clWindow, etc, etc.
679699
680700To implement support for system colors the WinAPI routine GetSysColor should be implemented:
681701
682-`function GetSysColor(nIndex: Integer): DWORD; override;`
702+``` pascal
703+function GetSysColor(nIndex: Integer): DWORD; override;
704+```
683705
684706And here is a snip of the color constants which should be supported. Check LCLType for the latest values:
685707
686708``` pascal
687709//==============================================
688710// API system Color constants pbd
689711// note these are usually shown ORed with
690712// $80000000 as these would have interfered with
691713// other MS color enumerations
692714// GetSysColor and SetSysColor expects the values
693715// below
694716//==============================================
695717
696718type
697719 COLORREF = LongInt;
698720 TColorRef = COLORREF;
699721
700722const
701723 CLR_INVALID = TColorRef($FFFFFFFF);
702724
703725 COLOR_SCROLLBAR = 0;
704726 COLOR_BACKGROUND = 1;
705727 COLOR_ACTIVECAPTION = 2;
706728 COLOR_INACTIVECAPTION = 3;
707729 COLOR_MENU = 4;
708730 COLOR_WINDOW = 5;
709731 COLOR_WINDOWFRAME = 6;
710732 COLOR_MENUTEXT = 7;
711733 COLOR_WINDOWTEXT = 8;
712734 COLOR_CAPTIONTEXT = 9;
713735 COLOR_ACTIVEBORDER = 10;
714736 COLOR_INACTIVEBORDER = 11;
715737 COLOR_APPWORKSPACE = 12;
716738 COLOR_HIGHLIGHT = 13;
717739 COLOR_HIGHLIGHTTEXT = 14;
718740 COLOR_BTNFACE = 15;
719741 COLOR_BTNSHADOW = 16;
720742 COLOR_GRAYTEXT = 17;
721743 COLOR_BTNTEXT = 18;
722744 COLOR_INACTIVECAPTIONTEXT = 19;
723745 COLOR_BTNHIGHLIGHT = 20;
724746 COLOR_3DDKSHADOW = 21;
725747 COLOR_3DLIGHT = 22;
726748 COLOR_INFOTEXT = 23;
727749 COLOR_INFOBK = 24;
728750 // PBD: 25 is unassigned in all the docs I can find
729751 // if someone finds what this is supposed to be then fill it in
730752 // note defaults below, and cl[ColorConst] in graphics
731753 COLOR_HOTLIGHT = 26;
732754 COLOR_GRADIENTACTIVECAPTION = 27;
733755 COLOR_GRADIENTINACTIVECAPTION = 28;
734756 COLOR_MENUHILIGHT = 29;
735757 COLOR_MENUBAR = 30;
736758
737759 COLOR_FORM = 31;
738760
739761 COLOR_ENDCOLORS = COLOR_FORM;
740762
741763 COLOR_DESKTOP = COLOR_BACKGROUND;
742764 COLOR_3DFACE = COLOR_BTNFACE;
743765 COLOR_3DSHADOW = COLOR_BTNSHADOW;
744766 COLOR_3DHIGHLIGHT = COLOR_BTNHIGHLIGHT;
745767 COLOR_3DHILIGHT = COLOR_BTNHIGHLIGHT;
746768 COLOR_BTNHILIGHT = COLOR_BTNHIGHLIGHT;
747769
748770 MAX_SYS_COLORS = COLOR_ENDCOLORS;
749771 SYS_COLOR_BASE = TColorRef($80000000);
750772```
751773
752774### ShowMessage
753775
754776These standard dialogs are implemented purely in the LCL in the following places:
755777
756778- Class TPromptDialog file lcl/include/promptdialog.inc
757779
758780### SpinEdit
759781
760782Both TFloatSpinEdit and TSpinEdit are implemented in the class TWSFloatSpinEdit.
761783
762784### Clipboard
763785
764786Clipboard support is implemented in lclintf by implementing Windows API routines. The routines are:
765787
766788``` pascal
767789function ClipboardFormatToMimeType(FormatID: TClipboardFormat): string; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
768790function ClipboardGetData(ClipboardType: TClipboardType;
769791 FormatID: TClipboardFormat; Stream: TStream): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
770792// ! ClipboardGetFormats: List will be created. You must free it yourself with FreeMem(List) !
771793function ClipboardGetFormats(ClipboardType: TClipboardType;
772794 var Count: integer; var List: PClipboardFormat): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
773795function ClipboardGetOwnerShip(ClipboardType: TClipboardType;
774796 OnRequestProc: TClipboardRequestEvent; FormatCount: integer;
775797 Formats: PClipboardFormat): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
776798function ClipboardRegisterFormat(const AMimeType: string): TClipboardFormat; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
777799```
778800
779801Sequence of calls during program startup:
780802
781803``` pascal
782804TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/bmp Result=51027040
783805TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/delphi.bitmap Result=0
784806TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/xpm Result=0
785807TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/png Result=0
786808TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/jpeg Result=0
787809```
788810
789811Sequence of calls during "Caption := Clipboard.AsText":
790812
791813``` pascal
792814TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=text/plain Result=51026976
793815TCocoaWidgetSet.ClipboardGetData ClipboardType=clipboard FormatID: 51026976
794816```
795817
796818### TSpeedButton
797819
798820This 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.
799821
800822### TRadioButton and TCheckButton
801823
802824**Messages:**
803825
804826Basically the Widgetset must do:
805827
806828- Send LM_CHANGE when a radio button is unchecked/checked (LCL will take care if OnClick will be called or not)
807829- Dont send LM_CHANGE when TWSCustomCheckBox.SetState is called (SetChecked)
808830
809831See also this bug report: <http://bugs.freepascal.org/view.php?id=13939>
810832
811833### FullScreen support
812834
813835FullScreen is implemented as a Windows state wsFullScreen, and as such is implemented in LCLIntf.ShowWindow where the constant SW_SHOWFULLSCREEN should be handled.
814836
815837## An example of how the LCL interfaces work
816838
817839Below 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?
818840
819841You would need to generate the following files:
820842
821843\trayicon.pas
822844
823845\wstrayicon.pas
824846
825847\gtk\gtkwstrayicon.pas
826848
827849\gtk\trayintf.pas
828850
829851\win32\win32wstrayicon.pas
830852
831853\win32\trayintf.pas
832854
833855Providing 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.
834856
835857Inside 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.
836858
837859``` pascal
838860unit TrayIcon;
839861
840862interface
841863
842864type
843865 TTrayIcon = class(TLCLComponent)
844866 public
845867 procedure DoTray;
846868 end;
847869
848870implementation
849871
850872uses wstrayicon;
851873
852874procedure TTrayIcon.DoTray;
853875begin
854876 // Call wstrayicon
855877end;
856878
857879end.
858880```
859881
860882in trayintf you use gtkwstrayicon or win32trayicon depending on which trayintf file it is.
861883
862884in wstrayicon you create a class like so:
863885
864886``` pascal
865887unit WSTrayIcon;
866888
867889uses WSLCLClasses, Controls, TrayIcon; // and other things as well
868890
869891TWSTrayIconClass = class of TWSTrayIcon;
870892TWSTrayIcon = class(TWSWinControl);
871893public
872894 class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
873895virtual; // these must all be virtual and class procedures!!
874896 class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); virtual;
875897 ....
876898end;
877899...
878900
879901implementation
880902
881903procedure TWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
882904begin
883905 //do nothing
884906end;
885907
886908procedure TWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
887909begin
888910 //do nothing
889911end;
890912```
891913
892914now in gtkwstrayicon.pas do this:
893915
894916``` pascal
895917uses WSTrayIcon, WSLCLClasses, Controls, TrayIcon, gtk, gdk;
896918
897919TGtkWSTrayIcon = class(TWSTrayIcon);
898920private
899921 class function FindSystemTray(const ATrayIcon: TCustomTrayIcon):
900922TWindow; virtual;
901923public
902924 class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); override;
903925 class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
904926override;
905927 class function CreateHandle(const AWinControl: TWinControl; const
906928AParams: TCreateParams): HWND; override;
907929 ....
908930end;
909931...
910932
911933implementation
912934
913935procedure TGtkWSTrayIcon.CreateHandle(const AWinControl: TWinControl;
914936const AParams: TCreateParams): HWND;
915937var
916938WidgetInfo: PWidgetInfo;
917939begin
918940
919941 Result := gtk_plug_new;
920942 WidgetInfo := CreateWidgetInfo(AWinControl, Result); // it's something
921943like this anyway
922944 TGtkWSWincontrolClass(WidgetSetClass).SetCallbacks(AWinControl);
923945 // and more stuff
924946end;
925947
926948function TGtkWSTrayIcon.FindSystemTray(const ATrayIcon:
927949TCustomTrayIcon): TWindow;
928950begin
929951 // do something
930952end;
931953
932954procedure TGtkWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
933955var
934956SystemTray: TWindow;
935957begin
936958 SystemTray := FindSystemTray(ATrayIcon);
937959 //do something
938960end;
939961
940962procedure TGtkWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
941963begin
942964 //do something
943965end;
944966
945967......
946968
947969initialization
948970
949971RegisterWSComponent(TCustomTrayIcon, TGtkWSTrayIcon); //this is very
950972important!!!
951973
952974end.
953975```
954976
955977then finally in trayicon.pas you go as normal
956978
957979``` pascal
958980uses WSTrayIcon; //etc. you DON'T include GtkWSTrayIcon here!
959981
960982TCustomTrayIcon = class(TWinControl)
961983public
962984 procedure EmbedControl;
963985....
964986end;
965987
966988...
967989procedure TTrayIcon.EmbedControl;
968990begin
969991 TWSTrayIconClass(WidgetSetClass).EmbedControl(Self);
970992
971993end;
972994```
973995
974996## Minimum Toolkit versions
975997
976998| 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 |
977999|----|----|----|----|----|----|----|----|----|
9781000| 0.9.24 | 2.2.0 | 2.6+ | 4.2+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
9791001| 0.9.26 | 2.2.2 | 2.6+ | 4.3+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
9801002| 0.9.28 | 2.2.4 | 2.8+ | 4.5+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
9811003| 0.9.30 | 2.4.0 | 2.8+ | 4.5+ | Windows 98+ | Recommended 5.0+ | 10.4 | N/A | N/A |
9821004| 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+ |
9831005| 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+ |
9841006
9851007- \- Actually 4.5.0 ...4.8.4 with pre-built Qt4Pas.dll, 4.8.5+ requires a different Qt4Pas.dll
9861008
9871009See Also: [[Installing Lazarus on macOS|FPC + Lazarus Compatibility Matrix]].
9881010
9891011---
9901012*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