Sign in Join
Wiki copytemporary

LCL Internals: what changed

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

Title: LCL Internals

OldNewLine
1+------------------------------------------------------------------------
2+
3+> **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.
4+
5+------------------------------------------------------------------------
6+
7+## Internals of the LCL
8+
9+There 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.
10+
11+Then 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.
12+
13+Every 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.
14+
15+Communication 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.
16+
17+### Pie and RadialPie
18+
19+The LCLIntf unit contains two functions to draw pie-shapes:
20+
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; `
23+
24+The 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.
25+
26+Pie 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.
27+
28+The win32 widgetset overrides TWidgetSet.Pie to call the Windows Pie function directly.
29+
30+> **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.
31+
32+### Adding a new unit to the LCL
33+
34+First add the new unit name into allclunits.pp.
35+
36+To make sure the unit is registered in the components palette see the files RegisterLCL.pas and pkgfileslcl.inc, they are located in lazarus/packager.
37+
38+## How to create a new Widgetset
39+
40+This 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.
41+
42+To 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.
43+
44+Now, 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.
45+
46+Now, for Qt I utilized Den Jean qt4 bindings for pascal, and created a very basic Qt program using them:
47+
48+``` pascal
49+program qttest;
50+
51+uses qt4;
52+
53+var
54+ App: QApplicationH;
55+ MainWindow: QMainWindowH;
56+begin
57+ App := QApplication_Create(@argc,argv);
58+
59+ MainWindow := QMainWindow_Create;
60+
61+ QWidget_show(MainWindow);
62+
63+ QApplication_Exec;
64+end.
65+```
66+
67+The 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:
68+
69+``` pascal
70+program qttest;
71+
72+{$mode objfpc}{$H+}
73+
74+uses
75+ Interfaces, Classes, Forms,
76+ { Add your units here }
77+ qtform;
78+
79+begin
80+ Application.Initialize;
81+ Application.CreateForm(TForm1, Form1);
82+ Application.Run;
83+end.
84+```
85+
86+Where the form is maintained by Lazarus IDE and designed visually.
87+
88+The 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.
89+
90+Looking 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:
91+
92+``` pascal
93+TQtWidgetSet = Class(TWidgetSet)
94+ private
95+ App: QApplicationH;
96+ public
97+ {$I qtwinapih.inc}
98+ {$I qtlclintfh.inc}
99+ public
100+ // Application
101+ procedure AppInit(var ScreenInfo: TScreenInfo); override;
102+ procedure AppRun(const ALoop: TApplicationMainLoop); override;
103+ procedure AppWaitMessage; override;
104+ procedure AppProcessMessages; override;
105+ procedure AppTerminate; override;
106+ procedure AppMinimize; override;
107+ procedure AppBringToFront; override;
108+ public
109+ constructor Create;
110+ destructor Destroy; override;
111+ function DCGetPixel(CanvasHandle: HDC; X, Y: integer): TGraphicsColor; override;
112+ procedure DCSetPixel(CanvasHandle: HDC; X, Y: integer; AColor: TGraphicsColor); override;
113+ procedure DCRedraw(CanvasHandle: HDC); override;
114+ procedure SetDesigning(AComponent: TComponent); override;
115+
116+ function InitHintFont(HintFont: TObject): Boolean; override;
117+
118+ // create and destroy
119+ function CreateComponent(Sender : TObject): THandle; override; // deprecated
120+ function CreateTimer(Interval: integer; TimerFunc: TFNTimerProc): integer; override;
121+ function DestroyTimer(TimerHandle: integer): boolean; override;
122+ end;
123+```
124+
125+### How to implement a new windowed component
126+
127+Windowed 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.
128+
129+Let'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.
130+
131+Now, go to QtWSStrCtrls unit and look for the declaration of TQtWSCustomEdit.
132+
133+``` pascal
134+
135+TQtWSCustomEdit = class(TWSCustomEdit)
136+ private
137+ protected
138+ public
139+ end;
140+```
141+
142+Add static methods that are declared on TWSCustomEdit and override them. The code should now look like this:
143+
144+``` pascal
145+
146+TQtWSCustomEdit = class(TWSCustomEdit)
147+ private
148+ protected
149+ public
150+ class function CreateHandle(const AWinControl: TWinControl;
151+ const AParams: TCreateParams): HWND; override;
152+ class procedure DestroyHandle(const AWinControl: TWinControl); override;
153+{ class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override;
154+ class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
155+
156+ class procedure SetCharCase(const ACustomEdit: TCustomEdit; NewCase: TEditCharCase); override;
157+ class procedure SetEchoMode(const ACustomEdit: TCustomEdit; NewMode: TEchoMode); override;
158+ class procedure SetMaxLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
159+ class procedure SetPasswordChar(const ACustomEdit: TCustomEdit; NewChar: char); override;
160+ class procedure SetReadOnly(const ACustomEdit: TCustomEdit; NewReadOnly: boolean); override;
161+ class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
162+ class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
163+
164+ class procedure GetPreferredSize(const AWinControl: TWinControl;
165+ var PreferredWidth, PreferredHeight: integer); override;}
166+ end;
167+```
168+
169+The 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.
170+
171+Hit CTRL+SHIFT+C to code complete and the implement CreateHandle and DestroyHandle. In the case of Qt4 the code will be like this:
172+
173+``` pascal
174+{ TQtWSCustomEdit }
175+
176+class function TQtWSCustomEdit.CreateHandle(const AWinControl: TWinControl;
177+ const AParams: TCreateParams): HWND;
178+var
179+ Widget: QWidgetH;
180+ Str: WideString;
181+begin
182+ // Creates the widget
183+ WriteLn('Calling QTextDocument_create');
184+ Str := WideString((AWinControl as TCustomMemo).Lines.Text);
185+ Widget := QTextEdit_create(@Str, QWidgetH(AWinControl.Parent.Handle));
186+
187+ // Sets it's initial properties
188+ QWidget_setGeometry(Widget, AWinControl.Left, AWinControl.Top,
189+ AWinControl.Width, AWinControl.Height);
190+
191+ QWidget_show(Widget);
192+
193+ Result := THandle(Widget);
194+end;
195+
196+class procedure TQtWSCustomEdit.DestroyHandle(const AWinControl: TWinControl);
197+begin
198+ QTextEdit_destroy(QTextEditH(AWinControl.Handle));
199+end;
200+```
201+
202+Now uncomment the like "RegisterWSComponent(TCustomEdit, TQtWSCustomEdit);" on the bottom of the unit and that's it!
203+
204+You can now drop a TCustomEdit on the bottom of a form and expect it to work. :^)
205+
206+### Implementing TBitmap
207+
208+To 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]]
209+
210+So, let's say you want to compile the following code:
211+
212+``` pascal
213+procedure TMyForm.HandleOnPaint(Sender: TObject);
214+var
215+ Bitmap: TBitmap;
216+begin
217+ Bitmap := TBitmap.Create;
218+ try
219+ Bitmap.LoadFromFile('myfile.bmp');
220+ Canvas.Draw(0, 0, Bitmap);
221+ finally
222+ Bitmap.Free;
223+ end;
224+end;
225+```
226+
227+Below is the order on which functions from the widgetset interface are called when executing that code:
228+
229+1 - BeginPaint
230+
231+This will be called only if the OnPaint event sends zero as the DC for the paint event
232+
233+2 - GetDC(0);
234+
235+Just create a device context.
236+
237+3 - TCDWidgetSet.RawImage_QueryDescription
238+
239+The default implementation of this routine is good for most widgetsets
240+
241+4 - TCDWidgetSet.RawImage_CreateBitmaps
242+
243+Here 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.
244+
245+5 - CreateCompatibleDC(0)
246+
247+This 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
248+
249+6 - SelectObject
250+
251+With the image as the object to be selected and the DC just created above as target DC.
252+
253+7 - StretchMaskBlt
254+
255+Finally the drawing function! DestDC is the DC allocated on BeginPaint.
256+
257+8 - EndPaint
258+
259+Again, not always utilized.
260+
261+### TBitmap.LoadFromDevice for screenshot taking
262+
263+It is recomended that you first implement TBitmap before trying this step.
264+
265+On LCL you can use the following code takes a screenshot from the entire screen and paints it on the canvas:
266+
267+``` pascal
268+var
269+ ScreenDC: HDC;
270+ Bitmap: TBitmap;
271+begin
272+ Bitmap := TBitmap.Create;
273+ try
274+ ScreenDC := GetDC(0);
275+ Bitmap.LoadFromDevice(ScreenDC);
276+ ReleaseDC(0, ScreenDC);
277+ Canvas.Draw(0, 0, Bitmap);
278+ finally
279+ Bitmap.Free;
280+ end;
281+end;
282+```
283+
284+If you already implemented TBitmap, there are only 2 new functions to be implemented for LoadFromDevice: GetDeviceSize and GetRawImageFromDevice
285+
286+Below 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.
287+
288+` [WinAPI BeginPaint] Result=-1220713544`\
289+` [WinAPI GetClientBounds]`\
290+` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0`
291+
292+\<syntaxhighlight lang="pascal\> //Enters on Paint event
293+
294+Bitmap := TBitmap.Create; try
295+
296+` ScreenDC := GetDC(0);`
297+
298+</syntaxhighlight>
299+
300+` [WinAPI GetDC] hWnd: 0 Result: -1220712920`
301+
302+``` pascal
303+ Bitmap.LoadFromDevice(ScreenDC);
304+```
305+
306+` [WinAPI GetDeviceSize]`\
307+` [WinAPI GetRawImageFromDevice] SrcDC: -1220712920 SrcWidth: 0 SrcHeight: 0`\
308+` [WinAPI CreateBitmapFromRawImage] Width:1024 Height:768 DataSize: 3145728 CreateMask: False Bitmap:-1220746696`\
309+` [WinAPI GetObject] GDIObj: -1220746696 Result=84 ObjectType=Image`
310+
311+``` pascal
312+ ReleaseDC(0, ScreenDC);
313+```
314+
315+` [WinAPI ReleaseDC] hWnd: 0 DC: -1220712920`
316+
317+``` pascal
318+ Canvas.Draw(0, 0, Bitmap);
319+```
320+
321+` [WinAPI CreateCompatibleDC] DC: 0`\
322+` [WinAPI GetDC] hWnd: 0 Result: -1220712920`\
323+` [WinAPI SelectObject] DC=-1220712920 GDIObj=-1220746696 Result=0 ObjectType=Image`\
324+` [WinAPI StretchMaskBlt] DestDC:-1220713544 SrcDC:-1220712920 Image:137185120 X:0 Y:0 W:1024 H:768 XSrc:0 YSrc:0 WSrc:1024 HSrc:768`
325+
326+``` pascal
327+finally
328+ Bitmap.Free;
329+end;
330+```
331+
332+` [WinAPI SelectObject] DC=-1220712920 GDIObj=0 Invalid GDI Object`\
333+` [WinAPI DeleteObject] GDIObject: -1220746696 Result=False ObjectType=Image`
334+
335+Now exited the OnPaint event
336+
337+` [WinAPI DeleteObject] GDIObject: 0`\
338+` [WinAPI DeleteObject] GDIObject: 0`\
339+` [WinAPI DeleteObject] GDIObject: 0`\
340+` [WinAPI DeleteObject] GDIObject: 0`\
341+` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: -152 NewY: -246`\
342+` [WinAPI DeleteObject] GDIObject: 0`\
343+` [WinAPI DeleteObject] GDIObject: 0`\
344+` [WinAPI DeleteObject] GDIObject: 0`\
345+` [WinAPI DeleteObject] GDIObject: 0`
346+
347+``` pascal
348+TWidgetSet.InitializeCriticalSection
349+TWidgetSet.EnterCriticalSection
350+```
351+
352+` [WinAPI SelectObject] DC=-1220713544 GDIObj=0 Invalid GDI Object`\
353+` [WinAPI MoveToEx] DC:-1220713544 X:0 Y:0`\
354+` [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746760 Result=-1220746856 ObjectType=Brush`\
355+` [WinAPI SelectObject] DC=-1220713544 GDIObj=-1220746856 Result=-1220746856 ObjectType=Brush`
356+
357+``` pascal
358+TWidgetSet.LeaveCriticalSection
359+```
360+
361+` [WinAPI SetWindowOrgEx] DC: -1220713544 NewX: 0 NewY: 0`\
362+` [WinAPI EndPaint] Handle: -1220611768 PS.HDC: -1220713544`
363+
364+### Implementing drawing in the OnPaint event of a form or another control
365+
366+For 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.
367+
368+Here is the OnPaint event handler from the Cocoa widgetset which shows how it calls LCLSentPaintMsg:
369+
370+``` pascal
371+procedure TLCLCommonCallback.Draw(ControlContext: NSGraphicsContext;
372+ const bounds, dirty:NSRect);
373+var
374+ struct : TPaintStruct;
375+begin
376+ if not Assigned(Context) then Context:=TCocoaContext.Create;
377+
378+ Context.ctx:=ControlContext;
379+ if Context.InitDraw(Round(bounds.size.width), Round(bounds.size.height)) then
380+ begin
381+ FillChar(struct, SizeOf(TPaintStruct), 0);
382+ struct.hdc := HDC(Context);
383+ {$IFDEF VerboseWinAPI}
384+ DebugLn(Format('[TLCLCommonCallback.Draw] OnPaint event started context: %x', [HDC(context)]));
385+ {$ENDIF}
386+ LCLSendPaintMsg(Target, HDC(Context), @struct);
387+ {$IFDEF VerboseWinAPI}
388+ DebugLn('[TLCLCommonCallback.Draw] OnPaint event ended');
389+ {$ENDIF}
390+ end;
391+end;
392+```
393+
394+And a list of WinAPI routines which were called when running this event:
395+
396+ [TCocoaWidgetSet.GetDC] hWnd: 0 Result: 12400C0
397+ [TLCLCommonCallback.Draw] OnPaint event started context: 1240340
398+ TCocoaWidgetSet.CreatePenIndirect
399+ TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EC460
400+ TCocoaWidgetSet.SelectObject Result: 0
401+ TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EBF00
402+ TCocoaWidgetSet.SelectObject Result: 0
403+ [TCocoaWidgetSet.Rectangle] DC: 1240340 X1: 100 Y1: 100 X2: 200 Y2: 200
404+ TCocoaWidgetSet.SelectObject DC: 1240340 GDIObj: 10EC4A0
405+ TCocoaWidgetSet.SelectObject Result: 0
406+ [TCocoaWidgetSet.GetTextExtentPoint] DC: 1240340 Str: Some text Count: 9
407+ [TCocoaWidgetSet.GetTextExtentPoint] Size: 65,17
408+ [TLCLCommonCallback.Draw] OnPaint event ended
409+
410+### Implementing TLabel
411+
412+Implementing 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.
413+
414+Before trying to get TLabel working it is recomended to test if drawing functions such as Rectangle work inside a form's OnPaint event.
415+
416+Several WinAPI methods need to be implemented, particularly:
417+
418+**Device Context Methods**
419+
420+BeginPaint, GetDC, EndPaint, ReleaseDC, CreateCompatibleDC
421+
422+see [[Device Contexts and GDI objects in the LCL interfaces]]
423+
424+**GDI Objects Methods**
425+
426+SelectObject, DeleteObject, CreateFontIndirect, CreateFontIndirectEx
427+
428+**Miscelaneous functions**
429+
430+InvalidateRect, GetClientBounds, SetWindowOrgEx
431+
432+**Text drawing Methods**
433+
434+DrawText. 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.
435+
436+**Region functions to determine if the control is behind another**
437+
438+CombineRgn, CreateRectRgn, GetClipRGN, RectVisible
439+
440+Below is the order in which paint procedures are called on a form with only one TLabel, to better understand the painting sequence:
441+
442+1 - GetDC is called once on software startup with hWnd = 0
443+
444+2 - The form is shown
445+
446+3 - 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.
447+
448+4 - InvalidateRect is called on the form canvas
449+
450+5 - Control goes back to the operating system until a paint message comes from the widgetset
451+
452+6 - BeginPaint is called, and at this point code on OnPaint event of the form will be executed
453+
454+7 - DrawText is called again with CalcRect set to false
455+
456+8 - The Painting ends.
457+
458+### Implementing visibility for forms and controls and window state
459+
460+The code that controls visibility is split between visibility for forms, and for controls
461+
462+**Visibility for forms and window state**
463+
464+This 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
465+
466+Below 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.
467+
468+``` pascal
469+{------------------------------------------------------------------------------
470+ function ShowWindow(hWnd: HWND; nCmdShow: Integer): Boolean;
471+
472+ nCmdShow:
473+ SW_SHOWNORMAL, SW_MINIMIZE, SW_SHOWMAXIMIZED
474+------------------------------------------------------------------------------}
475+function TQtWidgetSet.ShowWindow(hWnd: HWND; nCmdShow: Integer): Boolean;
476+var
477+ Widget: QWidgetH;
478+begin
479+ {$ifdef VerboseQtWinAPI}
480+ WriteLn('WinAPI ShowWindow');
481+ {$endif}
482+
483+ Result := False;
484+
485+ Widget := QWidgetH(hWnd);
486+
487+// if Widget = nil then RaiseException('TQtWidgetSet.ShowWindow hWnd is nil');
488+
489+ case nCmdShow of
490+
491+ SW_SHOW: QWidget_setVisible(Widget, True);
492+
493+ SW_SHOWNORMAL: QWidget_showNormal(Widget);
494+
495+ SW_MINIMIZE: QWidget_setWindowState(Widget, QtWindowMinimized);
496+
497+ SW_SHOWMINIMIZED: QWidget_showMinimized(Widget);
498+
499+ SW_SHOWMAXIMIZED: QWidget_showMaximized(Widget);
500+
501+ SW_HIDE: QWidget_setVisible(Widget, False);
502+
503+ end;
504+
505+ Result := True;
506+end;
507+```
508+
509+**Visibility for controls**
510+
511+For controls inside a form you need to implement TMyWSWinControl.ShowHide class function that resides on the TMyWSWinControl class on the file mywscontrols.pp
512+
513+Remember 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.
514+
515+``` pascal
516+{------------------------------------------------------------------------------
517+ Method: TQtWSWinControl.ShowHide
518+ Params: AWinControl - the calling object
519+
520+ Returns: Nothing
521+
522+ Shows or hides a widget.
523+ ------------------------------------------------------------------------------}
524+class procedure TQtWSWinControl.ShowHide(const AWinControl: TWinControl);
525+begin
526+ if AWinControl = nil then exit;
527+
528+ if not AWinControl.HandleAllocated then exit;
529+
530+ if AWinControl.HandleObjectShouldBeVisible then
531+ QWidget_setVisible(TQtWidget(AWinControl.Handle).Widget, True)
532+ else QWidget_setVisible(TQtWidget(AWinControl.Handle).Widget, False);
533+end;
534+```
535+
536+### Implementing TStrings based Components
537+
538+Some components use a TStrings to store the information they display, such as: TCustomMemo, TCustomListBox and TCustomComboBox.
539+
540+To 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:
541+
542+``` pascal
543+class function TQtWSCustomListBox.GetStrings(const ACustomListBox: TCustomListBox): TStrings;
544+var
545+ ListWidgetH: QListWidgetH;
546+begin
547+ ListWidgetH := QListWidgetH((TQtWidget(ACustomListBox.Handle).Widget));
548+ Result := TQtListStrings.Create(ListWidgetH, ACustomListBox);
549+end;
550+```
551+
552+This 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:
553+
554+``` pascal
555+
556+TQtListStrings = class(TStrings)
557+ private
558+ FListChanged: Boolean; // StringList and QtListWidget out of sync
559+ FStringList: TStringList; // Holds the items to show
560+ FQtListWidget: QListWidgetH; // Qt Widget
561+ FOwner: TWinControl; // Lazarus Control Owning ListStrings
562+ FUpdating: Boolean; // We're changing Qt Widget
563+ procedure InternalUpdate;
564+ procedure ExternalUpdate(var Astr: TStringList; Clear: Boolean = True);
565+ procedure IsChanged; // OnChange triggered by program action
566+ protected
567+ function GetTextStr: string; override;
568+ function GetCount: integer; override;
569+ function Get(Index : Integer) : string; override;
570+ //procedure SetSorted(Val : boolean); virtual;
571+ public
572+ constructor Create(ListWidgetH : QListWidgetH; TheOwner: TWinControl);
573+ destructor Destroy; override;
574+ procedure Assign(Source : TPersistent); override;
575+ procedure Clear; override;
576+ procedure Delete(Index : integer); override;
577+ procedure Insert(Index : integer; const S: string); override;
578+ procedure SetText(TheText: PChar); override;
579+ //procedure Sort; virtual;
580+ public
581+ //property Sorted: boolean read FSorted write SetSorted;
582+ property Owner: TWinControl read FOwner;
583+ function ListChangedHandler(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
584+ end;
585+```
586+
587+You can see its implementation in the qtobjects.pas unit of the qt interface
588+
589+### Implementing Menus
590+
591+Menus 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.
592+
593+Also 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.
594+
595+The following things need to be implemented in order for the menus to work:
596+
597+1\) All methods on the QtWSMenus unit, which will implement menu creation and modification
598+
599+2\) 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.
600+
601+#### Menu Creation Order
602+
603+One important thing to understand when implementing menus, is in which order they are created. For example, we want to create the following menu structure:
604+
605+<figure>
606+<img src="Menu_creation_order.png" title="Menu_creation_order.png" />
607+<figcaption>Menu_creation_order.png</figcaption>
608+</figure>
609+
610+And 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.
611+
612+Here is the resulting output of such software:
613+
614+ Creating Menu. Name: MainMenu1
615+ Creating MenuItem: Item1 Parent=Menu.Items : TMenuItem
616+ Creating MenuItem: SubItem11 Parent=Item1 : TMenuItem
617+ Creating MenuItem: SubItem12 Parent=Item1 : TMenuItem
618+ Creating MenuItem: SubItem13 Parent=Item1 : TMenuItem
619+ Creating MenuItem: SubItem14 Parent=Item1 : TMenuItem
620+ Creating MenuItem: SubSubItem141 Parent=SubItem14 : TMenuItem
621+ Creating MenuItem: SubSubItem142 Parent=SubItem14 : TMenuItem
622+ Creating MenuItem: SubSubItem143 Parent=SubItem14 : TMenuItem
623+ Creating MenuItem: SubSubItem144 Parent=SubItem14 : TMenuItem
624+ Creating MenuItem: Item2 Parent=Menu.Items : TMenuItem
625+ Creating MenuItem: SubItem21 Parent=Item2 : TMenuItem
626+ Creating MenuItem: SubItem22 Parent=Item2 : TMenuItem
627+ Creating MenuItem: SubItem23 Parent=Item2 : TMenuItem
628+ Creating MenuItem: Item3 Parent=Menu.Items : TMenuItem
629+ Creating MenuItem: Item4 Parent=Menu.Items : TMenuItem
630+
631+For all MenuItems one can use GetParentMenu to get their parent owner: Menu (TMainMenu).
632+
633+### Control enabling/disabling
634+
635+The 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.
636+
637+``` pascal
638+{------------------------------------------------------------------------------
639+ Method: EnableWindow
640+ Params: HWnd - handle to window
641+ BEnable - whether to enable the window
642+ Returns: If the window was previously disabled
643+
644+ Enables or disables mouse and keyboard input to the specified window or
645+ control.
646+ ------------------------------------------------------------------------------}
647+function TWin32WidgetSet.EnableWindow(hWnd: HWND; bEnable: Boolean): Boolean;
648+```
649+
650+### Return Control Text (Captions)
651+
652+The method to return control text is GetText().
653+
654+However, 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).
655+
656+If the text itself is considered to be "static" then method can return FALSE at all times.
657+
658+LCL caches the text most of the time, and thus if control returns false, it would pull the cached value.
659+
660+``` pascal
661+{------------------------------------------------------------------------------
662+ Function: TWSWinControl.GetText
663+ Params: Sender: The control to retrieve the text from
664+ Returns: the requested text
665+
666+ Retrieves the text from a control.
667+ ------------------------------------------------------------------------------}
668+class function TWSWinControl.GetText(const AWinControl: TWinControl; var AText: String): Boolean;
669+```
670+
671+### Shaped Windows
672+
673+Windows can be shaped based on a TBitmap or on a TRegion. The region is the visible part.
674+
675+To implement shaped windows based on a TBitmap, implement TWSWinControl.setShape
676+
677+To implement shaped windows based on a TRegion implement LCLIntf.SetWindowRgn
678+
679+See also: [[LCL Tips|LCL Tips#Creating a non-rectangular window or control]]
680+
681+### System colors
682+
683+Some color constants are actually system colors, like clBtnFace, clForm, clWindow, etc, etc.
684+
685+To implement support for system colors the WinAPI routine GetSysColor should be implemented:
686+
687+`function GetSysColor(nIndex: Integer): DWORD; override;`
688+
689+And here is a snip of the color constants which should be supported. Check LCLType for the latest values:
690+
691+``` pascal
692+//==============================================
693+// API system Color constants pbd
694+// note these are usually shown ORed with
695+// $80000000 as these would have interfered with
696+// other MS color enumerations
697+// GetSysColor and SetSysColor expects the values
698+// below
699+//==============================================
700+
701+type
702+ COLORREF = LongInt;
703+ TColorRef = COLORREF;
704+
705+const
706+ CLR_INVALID = TColorRef($FFFFFFFF);
707+
708+ COLOR_SCROLLBAR = 0;
709+ COLOR_BACKGROUND = 1;
710+ COLOR_ACTIVECAPTION = 2;
711+ COLOR_INACTIVECAPTION = 3;
712+ COLOR_MENU = 4;
713+ COLOR_WINDOW = 5;
714+ COLOR_WINDOWFRAME = 6;
715+ COLOR_MENUTEXT = 7;
716+ COLOR_WINDOWTEXT = 8;
717+ COLOR_CAPTIONTEXT = 9;
718+ COLOR_ACTIVEBORDER = 10;
719+ COLOR_INACTIVEBORDER = 11;
720+ COLOR_APPWORKSPACE = 12;
721+ COLOR_HIGHLIGHT = 13;
722+ COLOR_HIGHLIGHTTEXT = 14;
723+ COLOR_BTNFACE = 15;
724+ COLOR_BTNSHADOW = 16;
725+ COLOR_GRAYTEXT = 17;
726+ COLOR_BTNTEXT = 18;
727+ COLOR_INACTIVECAPTIONTEXT = 19;
728+ COLOR_BTNHIGHLIGHT = 20;
729+ COLOR_3DDKSHADOW = 21;
730+ COLOR_3DLIGHT = 22;
731+ COLOR_INFOTEXT = 23;
732+ COLOR_INFOBK = 24;
733+ // PBD: 25 is unassigned in all the docs I can find
734+ // if someone finds what this is supposed to be then fill it in
735+ // note defaults below, and cl[ColorConst] in graphics
736+ COLOR_HOTLIGHT = 26;
737+ COLOR_GRADIENTACTIVECAPTION = 27;
738+ COLOR_GRADIENTINACTIVECAPTION = 28;
739+ COLOR_MENUHILIGHT = 29;
740+ COLOR_MENUBAR = 30;
741+
742+ COLOR_FORM = 31;
743+
744+ COLOR_ENDCOLORS = COLOR_FORM;
745+
746+ COLOR_DESKTOP = COLOR_BACKGROUND;
747+ COLOR_3DFACE = COLOR_BTNFACE;
748+ COLOR_3DSHADOW = COLOR_BTNSHADOW;
749+ COLOR_3DHIGHLIGHT = COLOR_BTNHIGHLIGHT;
750+ COLOR_3DHILIGHT = COLOR_BTNHIGHLIGHT;
751+ COLOR_BTNHILIGHT = COLOR_BTNHIGHLIGHT;
752+
753+ MAX_SYS_COLORS = COLOR_ENDCOLORS;
754+ SYS_COLOR_BASE = TColorRef($80000000);
755+```
756+
757+### ShowMessage
758+
759+These standard dialogs are implemented purely in the LCL in the following places:
760+
761+- Class TPromptDialog file lcl/include/promptdialog.inc
762+
763+### SpinEdit
764+
765+Both TFloatSpinEdit and TSpinEdit are implemented in the class TWSFloatSpinEdit.
766+
767+### Clipboard
768+
769+Clipboard support is implemented in lclintf by implementing Windows API routines. The routines are:
770+
771+``` pascal
772+function ClipboardFormatToMimeType(FormatID: TClipboardFormat): string; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
773+function ClipboardGetData(ClipboardType: TClipboardType;
774+ FormatID: TClipboardFormat; Stream: TStream): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
775+// ! ClipboardGetFormats: List will be created. You must free it yourself with FreeMem(List) !
776+function ClipboardGetFormats(ClipboardType: TClipboardType;
777+ var Count: integer; var List: PClipboardFormat): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
778+function ClipboardGetOwnerShip(ClipboardType: TClipboardType;
779+ OnRequestProc: TClipboardRequestEvent; FormatCount: integer;
780+ Formats: PClipboardFormat): boolean; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
781+function ClipboardRegisterFormat(const AMimeType: string): TClipboardFormat; {$IFDEF IF_BASE_MEMBER}virtual;{$ENDIF}
782+```
783+
784+Sequence of calls during program startup:
785+
786+``` pascal
787+TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/bmp Result=51027040
788+TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/delphi.bitmap Result=0
789+TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/xpm Result=0
790+TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/png Result=0
791+TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=image/jpeg Result=0
792+```
793+
794+Sequence of calls during "Caption := Clipboard.AsText":
795+
796+``` pascal
797+TCocoaWidgetSet.ClipboardRegisterFormat AMimeType=text/plain Result=51026976
798+TCocoaWidgetSet.ClipboardGetData ClipboardType=clipboard FormatID: 51026976
799+```
800+
801+### TSpeedButton
802+
803+This 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.
804+
805+### TRadioButton and TCheckButton
806+
807+**Messages:**
808+
809+Basically the Widgetset must do:
810+
811+- Send LM_CHANGE when a radio button is unchecked/checked (LCL will take care if OnClick will be called or not)
812+- Dont send LM_CHANGE when TWSCustomCheckBox.SetState is called (SetChecked)
813+
814+See also this bug report: <http://bugs.freepascal.org/view.php?id=13939>
815+
816+### FullScreen support
817+
818+FullScreen is implemented as a Windows state wsFullScreen, and as such is implemented in LCLIntf.ShowWindow where the constant SW_SHOWFULLSCREEN should be handled.
819+
820+## An example of how the LCL interfaces work
821+
822+Below 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?
823+
824+You would need to generate the following files:
825+
826+\trayicon.pas
827+
828+\wstrayicon.pas
829+
830+\gtk\gtkwstrayicon.pas
831+
832+\gtk\trayintf.pas
833+
834+\win32\win32wstrayicon.pas
835+
836+\win32\trayintf.pas
837+
838+Providing 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.
839+
840+Inside 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.
841+
842+``` pascal
843+unit TrayIcon;
844+
845+interface
846+
847+type
848+ TTrayIcon = class(TLCLComponent)
849+ public
850+ procedure DoTray;
851+ end;
852+
853+implementation
854+
855+uses wstrayicon;
856+
857+procedure TTrayIcon.DoTray;
858+begin
859+ // Call wstrayicon
860+end;
861+
862+end.
863+```
864+
865+in trayintf you use gtkwstrayicon or win32trayicon depending on which trayintf file it is.
866+
867+in wstrayicon you create a class like so:
868+
869+``` pascal
870+unit WSTrayIcon;
871+
872+uses WSLCLClasses, Controls, TrayIcon; // and other things as well
873+
874+TWSTrayIconClass = class of TWSTrayIcon;
875+TWSTrayIcon = class(TWSWinControl);
876+public
877+ class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
878+virtual; // these must all be virtual and class procedures!!
879+ class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); virtual;
880+ ....
881+end;
882+...
883+
884+implementation
885+
886+procedure TWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
887+begin
888+ //do nothing
889+end;
890+
891+procedure TWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
892+begin
893+ //do nothing
894+end;
895+```
896+
897+now in gtkwstrayicon.pas do this:
898+
899+``` pascal
900+uses WSTrayIcon, WSLCLClasses, Controls, TrayIcon, gtk, gdk;
901+
902+TGtkWSTrayIcon = class(TWSTrayIcon);
903+private
904+ class function FindSystemTray(const ATrayIcon: TCustomTrayIcon):
905+TWindow; virtual;
906+public
907+ class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); override;
908+ class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
909+override;
910+ class function CreateHandle(const AWinControl: TWinControl; const
911+AParams: TCreateParams): HWND; override;
912+ ....
913+end;
914+...
915+
916+implementation
917+
918+procedure TGtkWSTrayIcon.CreateHandle(const AWinControl: TWinControl;
919+const AParams: TCreateParams): HWND;
920+var
921+WidgetInfo: PWidgetInfo;
922+begin
923+
924+ Result := gtk_plug_new;
925+ WidgetInfo := CreateWidgetInfo(AWinControl, Result); // it's something
926+like this anyway
927+ TGtkWSWincontrolClass(WidgetSetClass).SetCallbacks(AWinControl);
928+ // and more stuff
929+end;
930+
931+function TGtkWSTrayIcon.FindSystemTray(const ATrayIcon:
932+TCustomTrayIcon): TWindow;
933+begin
934+ // do something
935+end;
936+
937+procedure TGtkWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon);
938+var
939+SystemTray: TWindow;
940+begin
941+ SystemTray := FindSystemTray(ATrayIcon);
942+ //do something
943+end;
944+
945+procedure TGtkWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon);
946+begin
947+ //do something
948+end;
949+
950+......
951+
952+initialization
953+
954+RegisterWSComponent(TCustomTrayIcon, TGtkWSTrayIcon); //this is very
955+important!!!
956+
957+end.
958+```
959+
960+then finally in trayicon.pas you go as normal
961+
962+``` pascal
963+uses WSTrayIcon; //etc. you DON'T include GtkWSTrayIcon here!
964+
965+TCustomTrayIcon = class(TWinControl)
966+public
967+ procedure EmbedControl;
968+....
969+end;
970+
971+...
972+procedure TTrayIcon.EmbedControl;
973+begin
974+ TWSTrayIconClass(WidgetSetClass).EmbedControl(Self);
975+
976+end;
977+```
978+
979+## Minimum Toolkit versions
980+
981+| 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 |
982+|----|----|----|----|----|----|----|----|----|
983+| 0.9.24 | 2.2.0 | 2.6+ | 4.2+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
984+| 0.9.26 | 2.2.2 | 2.6+ | 4.3+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
985+| 0.9.28 | 2.2.4 | 2.8+ | 4.5+ | Windows 98+ | Recommended 4.0+ | 10.4 | N/A | N/A |
986+| 0.9.30 | 2.4.0 | 2.8+ | 4.5+ | Windows 98+ | Recommended 5.0+ | 10.4 | N/A | N/A |
987+| 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+ |
988+| 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+ |
989+
990+- \- Actually 4.5.0 ...4.8.4 with pre-built Qt4Pas.dll, 4.8.5+ requires a different Qt4Pas.dll
991+
992+See Also: [[Installing Lazarus on macOS|FPC + Lazarus Compatibility Matrix]].
993+
994+---
995+*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