Sign in Join
Wiki copytemporary

Lazarus Nongraphical Visual Component Example Code: what changed

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

Title: Lazarus Nongraphical Visual Component Example Code

OldNewLine
1+### Introduction
2+
3+There are many property types in Lazarus, and when writing a descendent of TComponent it is not always straightforward how to implement some of them. After searching many sources I have written a component template that includes most property types.
4+
5+The code below is a working component that could be packaged and compiled in the IDE, but its purpose is to serve as a source of copy/paste snippets.
6+
7+Here's what it looks like in the Lazarus Object Inspector:
8+
9+<figure>
10+<img src="myvisualcomponent_properties11.png" title="myvisualcomponent_properties11.png" />
11+<figcaption>myvisualcomponent_properties11.png</figcaption>
12+</figure>
13+
14+### Code to copy and paste
15+
16+``` pascal
17+unit MyCustomControl;
18+
19+{
20+= Example of a generic non-graphical component for Lazarus 1.x
21+= - example code for various types of properties and events to show and act correctly in the Object Inspector
22+= - including a custom 'About' dialog property editor
23+=
24+= Although this unit will compile as written, the purpose is to provide template code
25+= for various property types and events that can be copy/pasted into your own component
26+=
27+= Example Property types:
28+= 1) Bitmap
29+= 2) Font
30+= 3) Icon
31+= 4) Stringlist
32+= 5) Options expandable true/false list
33+= 6) User type drop-down list
34+= 7) Simple String
35+= 8) String with pre and post-processing
36+= 9) String Array with indexed properties
37+= 10) String with default value
38+= 11) Integer with default value
39+= 12) Overridden (custom) Tag property
40+= 13) Inherited drop-down list of types
41+= 14) Custom dialog
42+= 15) Custom events
43+= 16) Property hidden from the Object Inspector
44+= 17) Filename property with custom properties for the OpenDialog
45+=
46+= Author: minesadorada@charcodelvalle.com
47+= Date: May 2014
48+= License: LGPL
49+=
50+}
51+{$mode objfpc}{$H+}
52+
53+interface
54+
55+uses
56+ Classes, SysUtils, Forms, Controls, Graphics, Buttons,
57+ LCLIntf, LCLType, LResources,
58+ ExtCtrls, StdCtrls, Dialogs, PropEdits;
59+
60+const
61+ C_VERSION = '1.1'; // Remember to co-ordinate with the Package manager VersionInfo
62+
63+ C_ERRORMESSAGE = '%s error:' + LineEnding + '%s';
64+// Used with Exception.CreateFmt
65+
66+type
67+ // Drop-down list in Object Inspector
68+ tmcType = (mcType1, mcType2, mcType3);
69+
70+ // Expandable list of Options in Object Inspector
71+ tOptions = (Opt1, Opt2, Opt3);
72+ tOptionsFlags = set of tOptions;
73+
74+ tAboutString = string; // Unique string type used in 'About' property editor
75+ tFilenameString = string; // Unique string type used in MyFilename property editor
76+
77+ TSampleEvent = procedure(MyText: string) of object; // Custom Event type
78+
79+ TMyVisualComponent = class(TComponent) // Non-graphical ancestor
80+ private
81+ { Private declarations }
82+ fAboutString: string; // Dummy string for 'About' property
83+ fFileNameString: tFilenameString; // Filename
84+ fmcType: tmcType; // Type defined above
85+ fSimpleString: string; // Direct read and write (no methods)
86+ fProcessedString: string; // Methods used to read and write the property
87+ fStringWithDefault: string; // Object Inspector shows default
88+ fIntegerWithDefault: integer; // Object Inspector shows default
89+ fTag: string; // Property overrides normal Tag property
90+ fOptions: tOptionsFlags; // Variable of Set type. * If Opt1 IN fOptions then...
91+ fIcon: TIcon; // Assigned to Application Icon by default
92+ fFont: TFont; // Assigned only if MyFont property set
93+ fBitMap: TBitMap; // Assigned only if MyBitmap property set
94+ fSizeConstraints: TSizeConstraintsOptions;
95+ // Built-in set (part of TSizeConstraints object)
96+ fStringArray: array[0..3] of string;
97+ // Stores values of String1,String2,String3 and String4 properties
98+ fStringList: TStrings; // Holds MyStringList property values
99+ fVersion: string; // Holds read-only version property
100+ FOnSample: TSampleEvent; // Custom event
101+
102+ fHiddenString: string; // Hidden property
103+ fOnChangeHiddenString: TNotifyEvent; // Custom event
104+
105+ { Private methods }
106+
107+ procedure SetProcessedString(AValue: string);
108+ function GetProcessedString: string;
109+ // Read and Write procedures for property ProcessedString
110+
111+ procedure SetTag(AValue: string); // Write procedure for overridden property 'Tag'
112+
113+ function GetStringValue(const AIndex: integer): string;
114+ // Indexed Get Method
115+ procedure SetStringValue(const AIndex: integer; AValue: string);
116+ // Indexed Set method
117+
118+ procedure SetStrings(const AValue: TStrings);
119+ // Needed to use a TStrings property. FStringList also needs to be created in the Constructor
120+
121+ procedure SetFont(const AValue: TFont);
122+ // Needed to use a Font property. fFont also needs to be created in the Constructor
123+
124+ procedure SetBitMap(const AValue: TBitmap);
125+ // Needed to use a BitMap property. fBitMap also needs to be created in the Constructor
126+
127+ // Sets the HiddenString property
128+ procedure SetHiddenString(const AValue: string);
129+ protected
130+ { Protected declarations }
131+ // Can be used if you plan to subclass this component
132+ public
133+ { Public declarations }
134+ constructor Create(AOwner: TComponent); override; // Constructor must be public
135+ destructor Destroy; override; // Destructor must be public
136+
137+ { Public properties }
138+ // This section used for properties you don't want to display in the Object Inspector (or array properties)
139+ property HiddenString: string read fHiddenString write SetHiddenString;
140+ // Public property hidden from the Object Inspector. * SetHiddenString method can trigger custom event OnChangeHiddenString
141+ published
142+ {
143+ Published declarations (Displayed in the Object Inspector)
144+ Note: Comments immediately above the property declarations are displayed in the Lazarus Object Inspector lower pane
145+ }
146+
147+ // The custom property editor TAboutPropertyEditor is used in the Object Inspector
148+ property About: tAboutString read fAboutString; // Example of custom 'About' dialog
149+
150+ // Example of drop-down list in the Object Inspector
151+ // Default is displayed in different font and is set in Constructor
152+ property MyTypeList: tmcType read fmcType write fmcType default mcType2;
153+
154+ // Direct access read + write
155+ property SimpleString: string read fSimpleString write fSimpleString;
156+
157+ // Example of using Read and Write methods
158+ property ProcessedString: string read GetProcessedString write SetProcessedString;
159+
160+ // Example of pseudo-default property of a string (value is set in Constructor)
161+ property StringWithDefault: string read fStringWithDefault write fStringWithDefault;
162+
163+ // Example of an expandable list of booleans in the Object Inspector
164+ property Options: tOptionsFlags read fOptions write fOptions;
165+
166+ // Browse for Icon dialog in the Object Inspector. * No need to Create fIcon in constructor
167+ property MyIcon: TIcon read fIcon write fIcon;
168+
169+ // Browse for Font dialog in the Object Inspector. * fFont needs to be created in Constructor
170+ property MyFont: TFont read fFont write SetFont; // * Note the SetFont method
171+
172+ // * Note the SetBitmap method
173+ // Browse for Bitmap dialog in the Object Inspector. * fBitmap needs to be created in Constructor
174+ property MyBitMap: TBitmap read fBitMap write SetBitmap;
175+
176+ // Showing a built-in list of booleans in the Object Inspector
177+ property MySizeConstraints: TSizeConstraintsOptions
178+ read fSizeConstraints write fSizeConstraints;
179+
180+ // Default is displayed in different font but has to be set to the value 2 in Constructor
181+ property IntegerWithDefault: integer read fIntegerWithDefault
182+ write fIntegerWithDefault default 2; // Example of integer default
183+
184+ // The method is passed the index as a parameter
185+ // Example of using an indexed read and write method to store strings in an array
186+ property String1: string index 1 read GetStringValue write SetStringValue;
187+ // Example of using an indexed read and write method to store strings in an array
188+ property String2: string index 2 read GetStringValue write SetStringValue;
189+ // Example of using an indexed read and write method to store strings in an array
190+ property String3: string index 3 read GetStringValue write SetStringValue;
191+ // Example of using an indexed read and write method to store strings in an array
192+ property String4: string index 4 read GetStringValue write SetStringValue;
193+
194+ // Dialog shows in the Object Inspector. * fStringlist must be Created in the constructor. * Note the SetStrings override
195+ // String list example.
196+ property MyStringList: TStrings read fStringList write SetStrings;
197+
198+ // Example of a file open dialog property
199+ property MyFilename: tFilenameString read fFilenameString write fFilenameString;
200+
201+ // Tag property will now only accept a single alpha character
202+ // Example of overriding an ancestor (TComponent's) Tag property
203+ property Tag: string read fTag write SetTag;
204+
205+ // No need to specify type, read method or write method
206+ // Example of publishing a public property from an ancestor (TComponent)
207+ property ComponentState;
208+
209+ // ReadOnly property
210+ property Version: string read fVersion;
211+
212+ // Example of Custom Event that does nothing
213+ property OnSample: TSampleEvent read FOnSample write FOnSample;
214+
215+ // This custom event is always triggered by a change in the value of HiddenString property
216+ property OnChangeHiddenString: TNotifyEvent
217+ read fOnChangeHiddenString write fOnChangeHiddenString;
218+ end;
219+ // Declaration for the 'About' property editor
220+ TAboutPropertyEditor = class(TClassPropertyEditor)
221+ public
222+ procedure Edit; override;
223+ function GetAttributes: TPropertyAttributes; override;
224+ function GetValue: string; override;
225+ end;
226+ // Declaration for 'MyFilename' property editor
227+ // (See PropEdits unit for a list of property editors to modify)
228+ TMyFileNamePropertyEditor = class(TFileNamePropertyEditor)
229+ public
230+ // Override the Edit method for total control
231+ function GetFilter: string; override;
232+ function GetDialogOptions: TOpenOptions; override;
233+ function GetDialogTitle: string; override;
234+ end;
235+
236+procedure Register; // Must be declared here just before implementation
237+
238+implementation
239+
240+procedure Register;
241+begin
242+ {$I myvisualcomponent_icon.lrs}
243+ // Register this component into the IDE on the 'Additional' component palette
244+ RegisterComponents('Additional', [TMyVisualComponent]);
245+
246+ // Register the custom property editor for the 'About' property
247+ RegisterPropertyEditor(TypeInfo(tAboutString),
248+ TMyVisualComponent, 'About', TAboutPropertyEditor);
249+ // Register the custom property editor for the 'MyFilename' property
250+ RegisterPropertyEditor(TypeInfo(tFilenameString),
251+ TMyVisualComponent, 'MyFilename', TMyFileNamePropertyEditor);
252+
253+ // Note the TypeInfo parameter is made into a unique string type
254+ // (defined earlier in this unit)
255+ // so that the regular property editors in other components don't get confused
256+ // You could simply use TypeInfo(String)
257+end;
258+
259+constructor TMyVisualComponent.Create(AOwner: TComponent);
260+ // Called when form is loaded into the IDE
261+begin
262+ inherited Create(AOwner);
263+ // Initialisation goes here
264+ // Set any properties to their defaults and intialise objects
265+ fmcType := mcType2; // because this was the default value specified
266+ fStringWithDefault := 'Default String'; // Unspecified default
267+ fIntegerWithDefault := 2; // Default value specified
268+ fTag := 'A'; // Unspecified default
269+ fIcon := Application.Icon; // Unspecified default
270+ fFont := TFont.Create; // Needs to be created here for Object Inspector to show options
271+ fBitmap := TBitMap.Create;
272+ // Needs to be created here for Object Inspector to show the bitmap dialog
273+ fStringList := TStringList.Create;
274+ // Needs to be created here for Object Inspector to show dialog
275+ fVersion := C_VERSION;
276+ // Assign read-only property
277+end;
278+
279+destructor TMyVisualComponent.Destroy;
280+begin
281+ // Clean-up code goes here
282+ // FreeandNil any user-created objects here
283+ FreeAndNil(fBitmap);
284+ FreeAndNil(fStringList);
285+ FreeAndNil(fFont);
286+ inherited Destroy;
287+end;
288+
289+// == START PROPERTY EDITOR CODE ==
290+procedure TAboutPropertyEditor.Edit;
291+// Shows a dialog when About property is double-clicked
292+var
293+ tAboutForm: TForm;
294+ OKbutton: TBitBtn;
295+ lbl_Description: TLabel;
296+ sz: string;
297+begin
298+ // Make up message string
299+ sz := 'My component for Lazarus' + LineEnding + 'by email@domain.com' +
300+ LineEnding + LineEnding;
301+ sz += 'Methods:' + LineEnding;
302+ sz += 'MyVisualComponent.Method1' + LineEnding;
303+ sz += 'MyVisualComponent.Method2' + LineEnding;
304+ sz += LineEnding + 'Version: ' + C_VERSION + LineEnding;
305+ sz += 'License: LGPL';
306+ // Create a new dialog
307+ tAboutForm := TForm.CreateNew(nil);
308+ try //.. finally FreeAndNil everything
309+ with tAboutForm do
310+ begin
311+ // Set Form properties
312+ position := poScreenCenter;
313+ borderstyle := bsToolWindow;
314+ Caption := 'About My Component';
315+ formstyle := fsSystemStayOnTop;
316+ color := clSkyBlue;
317+ Height := 240;
318+ Width := 320;
319+ // Create a BitBtn button
320+ okbutton := TBitBtn.Create(tAboutForm);
321+ // Set BitBtn properties
322+ okbutton.Kind := bkClose;
323+ okbutton.left := (Width div 2) - okbutton.Width div 2;
324+ okbutton.top := Height - okbutton.Height - 10;
325+ okbutton.parent := tAboutForm;
326+ // Create a label control
327+ lbl_Description := Tlabel.Create(tAboutForm);
328+ // Set label properties
329+ lbl_Description.left := 8;
330+ lbl_Description.Top := 30;
331+ lbl_Description.Width := 304;
332+ lbl_Description.Height := 200;
333+ lbl_Description.Autosize := False;
334+ lbl_Description.Alignment := taCenter;
335+ lbl_Description.Caption := sz;
336+ lbl_Description.parent := tAboutForm;
337+ // Display the dialog modally
338+ ShowModal;
339+ end;
340+ finally
341+ // Free all resources
342+ FreeAndNil(lbl_Description);
343+ FreeAndNil(okbutton);
344+ FreeAndNil(tAboutForm);
345+ end;
346+end;
347+
348+function TAboutPropertyEditor.GetAttributes: TPropertyAttributes;
349+ // Show the ellipsis
350+begin
351+ Result := [paDialog];
352+end;
353+
354+function TAboutPropertyEditor.GetValue: string;
355+ // Override standard string read method
356+begin
357+ Result := '(Double-click)';
358+end;
359+
360+function TMyFileNamePropertyEditor.GetFilter: string;
361+begin
362+ Result := 'All Files|*.*|Bitmaps|*.bmp|JPegs|*.jpg';
363+end;
364+
365+function TMyFileNamePropertyEditor.GetDialogOptions: TOpenOptions;
366+begin
367+ // To see the full list, drop an OpenDialog onto a form and see the Options property
368+ Result := [ofFileMustExist, ofPathMustExist];
369+end;
370+
371+function TMyFileNamePropertyEditor.GetDialogTitle: string;
372+begin
373+ Result := 'My Custom Title';
374+end;
375+// == END PROPERTY EDITOR CODE ==
376+
377+// == PROPERTY GET/SETS ==
378+procedure TMyVisualComponent.SetProcessedString(AValue: string);
379+begin
380+ // Check to see if a change is necessary
381+ // Avalue can be amended before it is assigned to fProcessedString
382+ if (fProcessedString <> AValue) then
383+ fProcessedString := UpperCase(AValue);
384+end;
385+
386+function TMyVisualComponent.GetProcessedString: string;
387+begin
388+ // Do any error-checking or processing of fProcessedString here.
389+ Result := fProcessedString;
390+end;
391+
392+procedure TMyVisualComponent.SetTag(AValue: string);
393+// Overridden Tag property will only accept letters
394+// If changed via Object Inspector to a non-alpha string, then show custom error message
395+begin
396+ if (fTag <> AValue) and (Length(AValue) > 0) then
397+ if AValue[1] in ['A'..'Z'] + ['a'..'z'] then
398+ fTag := AValue[1]
399+ else
400+ if ComponentState = [csDesigning] then
401+ raise Exception.CreateFmt(C_ERRORMESSAGE,
402+ [Name, 'You can only set the tag property to an alpha value']);
403+ // 'Name' is the Name property of this TComponent instance
404+end;
405+
406+function TMyVisualComponent.GetStringValue(const AIndex: integer): string;
407+begin
408+ // Example of an indexed property Get method
409+ // Retrieve from private string Array
410+ Result := fStringArray[AIndex - 1];
411+end;
412+
413+procedure TMyVisualComponent.SetStringValue(const AIndex: integer; AValue: string);
414+// Example of an indexed property Set method
415+// Value is stored in private string array
416+begin
417+ if (fStringArray[AIndex - 1] <> AValue) then
418+ fStringArray[AIndex - 1] := AValue;
419+end;
420+
421+procedure TMyVisualComponent.SetStrings(const AValue: TStrings);
422+begin
423+ // this is correct statement
424+ FStringList.Assign(AValue);
425+ // this is not correct
426+ // FStrings := AValue;
427+end;
428+
429+procedure TMyVisualComponent.SetFont(const AValue: TFont);
430+begin
431+ // this is correct statement
432+ fFont.Assign(AValue);
433+ // this is not correct
434+ // fFont := AValue;
435+end;
436+
437+procedure TMyVisualComponent.SetBitMap(const AValue: TBitmap);
438+begin
439+ // this is correct statement
440+ fBitmap.Assign(AValue);
441+ // this is not correct
442+ // fBitmap := AValue;
443+end;
444+
445+procedure TMyVisualComponent.SetHiddenString(const AValue: string);
446+// If value is changed then triggers the custom events OnChangeHiddenString and OnSample
447+begin
448+ if (fHiddenString <> AValue) then
449+ begin
450+ fHiddenString := AValue;
451+ // Trigger custom events
452+ if Assigned(fOnChangeHiddenString) then
453+ OnChangeHiddenString(Self);
454+ if Assigned(fOnSample) then
455+ OnSample(AValue);
456+ end;
457+end;
458+// Sample application code that uses the Custom Events as coded
459+// Both are triggered by changing the HiddenString property in SetHiddenString
460+{
461+procedure TForm1.MyVisualComponent1ChangeHiddenString(Sender: TObject);
462+begin
463+ ShowMessage('Hidden String was changed to something new');
464+end;
465+
466+procedure TForm1.MyVisualComponent1Sample(MyText: String);
467+begin
468+ ShowMessageFmt('Hidden String was changed to "%s"',[MyText]);
469+end;
470+
471+procedure TForm1.Button1Click(Sender: TObject);
472+begin
473+ MyVisualComponent1.HiddenString:='Hello World';
474+end;
475+}
476+end.
477+```
478+
479+### Download
480+
481+You can download this code as a package ready to install and play with [here](http://www.charcodelvalle.com/myvisualcomponent/myvisualcomponent_code.zip)
482+
483+### Author
484+
485+minesadorada@charcodelvalle.com
486+
487+### License
488+
489+All code is released under the LGPL license
490+
491+## See also
492+
493+- [[Components and Code examples|Components and code examples]]
494+- [[How To Write Lazarus Component|How to write a Lazarus component]]
495+- [[Object Oriented Programming with FreePascal and Lazarus|Object-oriented programming with FreePascal and Lazarus]]
496+
497+---
498+*From the Free Pascal wiki, page "Lazarus Nongraphical Visual Component Example Code", as it stood on 2020-02-18 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).*
499+Category: Code Snippets
500+Category: Component Creation
501+Category: Object-oriented development
502+Category: Tutorials

Back to history