Sign in Join
Wiki copytemporary

Grids Reference Page: what changed

Revision 4 to 5. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Objective
22
33This text will try to show the user some aspects of the grids components in Lazarus. It is also intended to serve as a guide for users who have never used grids before (as experienced users generally only need a reference for new functionality). This text will therefore try to reach the following objectives:
44
55 1. To introduce the grids components to people with little or no previous Delphi experience.
66 2. To document the differences with respect to Delphi grids components.
77 3. To document the new functionality in Lazarus grids.
88 4. Create reference and examples for the components.
99
1010## Overview
1111
1212A grid is a component that provides a means for displaying data in tabular format. The most obvious characteristic of grids is that they are composed of cells forming rows and columns.
1313
1414The type of information that can be shown in a grid varies and mainly depends on what the user wants to show. Generally this information consists of text, colors, images or a combination of those three.
1515
1616Given the great variety of information that can be represented, a series of grids exist whose purpose is to facilitate the user in showing specific kinds of information. For instance, there is a grid designed to show text: the **StringGrid**. Documentation for that grid can be found [here](<http://lazarus-ccr.sourceforge.net/docs/lcl/grids/tstringgrid.html>)
1717
1818## Inheritance Tree
1919```
2020[[TCustomControl]]
2121 |
2222 |
2323 TCustomGrid
2424 |
2525 +-------------+------------+
2626 | |
2727 TCustomDrawGrid TCustomDbGrid
2828 | |
2929 +--------+--------+ |
3030 | | [[TDBGrid]]
3131 [[TDrawGrid]] TCustomStringGrid
3232 |
3333 ---------+----------
3434 | |
3535 [[TStringGrid]] [[TValueListEditor]]
3636```
3737
3838## A Starting Example
3939
4040As one of the objectives of this page is to help people with little or no previous Lazarus knowledge let's do a quick starting example of grids in action. Why not, let's make a traditional "hello world" example using the TStringGrid Component.
4141
4242 1. Create a new application.
4343 * From the main menu select: project->New Project
4444 * In the Create New Project dialog press the "Create Button"
4545 * A new empty form will be shown.
4646 2. Place a grid on the form
4747 * From the component palette select the "additional" tab
4848 * Click over the TStringGrid icon [![tstringgrid.png](/wiki-images/c/c4/tstringgrid.png)](</File:tstringgrid.png>)
4949 * Click over the form, near to the top left corner. A new empty grid appears.
5050 3. Place a button on the form
5151 * From the component palette select the "Standard" tab
5252 * Click over the TButton icon [![tbutton.png](/wiki-images/b/b2/tbutton.png)](</File:tbutton.png>)
5353 * Click over a empty area of the form. A new button appears.
5454 4. Doubleclick the button from step 3, and write down the following code in the click button handler:
5555 *
5656```
5757StringGrid1.Cells[1,1] := 'Hi World!';
5858```
5959
6060 5. Run the program by clicking the play icon [![menu run.png](/wiki-images/0/0d/menu_run.png)](</File:menu_run.png>)
6161 * by pressing the Button1, the hello world text should appear in cell column 1, row 1. (note that left column and top row are 0)
6262
6363## Differences between Lazarus and Delphi grids
6464
6565The current grid components differ from Delphi grids in several ways. When first developed the Lazarus grids were created from scratch without any attempt to make them fully Delphi-compatible.
6666
6767At a later stage, compatibility with Delphi's grids became a desired objective and the Lazarus grids started to conform more closely to the Delphi grid interface. However even this was done without attempting to make every single Lazarus grid property or method match its Delphi counterpart. Also (because Lazarus grid internals are very different from Delphi grid internals) some Delphi functionality is not possible or needs to be emulated differently in a Lazarus grid from how it would be done in a Delphi grid. We have achieved greater Delphi compatibility as the Lazarus grids have evolved, and this is desirable.
6868
6969### Differences
7070
7171Known differences are listed below, in no special order.
7272
7373 * Cell Editors
7474 * Designtime Behaviour
7575 * Cell Drawing has some differences, see customizing grid section.
7676
7777### New Functionality
7878
7979 * Custom Columns
8080 * Events
8181 * Grid Editor
8282
8383### Ways in which you can make a Lazarus grid more Delphi-compatible
8484
8585You can make a Lazarus grid look like and behave like a corresponding Delphi. Listed below are property settings which will achieve this. These adjustments are based in a newly created grid. Entries tagged with [Code] need to be set in code, [Design] entries can be changed at design time.
8686
8787 * [Design] TitleStyle := tsStandard;
8888 * [Design] DefaultRowHeight := 24;
8989 * [Code] EditorBorderStyle := bsNone; // this might work only on Windows.
9090 * [Code] UseXORFeatures := true;
9191 * [Code] AllowOutBoundEvents := False; // SVN revision 10992 or later
9292 * [Code] FastEditing := False; // supported in dbgrid. StringGrid requires SVN revision 10992 or later
9393 * [Design] AutoAdvance := aaNone;
9494
9595## Grids Reference
9696
9797### Information
9898
9999The starting point for reference about TCustomGrid, TDrawGrid, TCustomDrawGrid, TCustomStringGrid and TStringGrid is the [unit Grids.pas reference](<http://lazarus-ccr.sourceforge.net/docs/lcl/grids/index.html>)
100100
101101For TCustomDBGrid and TDBgrid it is the [unit DBGrids.pas reference](<http://lazarus-ccr.sourceforge.net/docs/lcl/dbgrids/index.html>)
102102
103103In general, any Delphi reference about the grids should help us to use Lazarus grids (but don't forget that there are several differences between Delphi and Lazarus grids that we have documented); with this in mind and as a temporary place for reference information, this place will be used to document things that don't work the same as in Delphi, as well as any new functionality.
104104
105105TODO: the rest of this section will disappear; its content will be moved to [unit Grids.pas reference](<http://lazarus-ccr.sourceforge.net/docs/lcl/grids/index.html>)
106106
107107### TCustomGrid
108108
109109See the full [TCustomGrid Reference](<http://lazarus-ccr.sourceforge.net/docs/lcl/grids/tcustomgrid.html> "doc:lcl/grids/tcustomgrid.html")
110110
111111#### property AllowOutboundEvents
112112
113113Protected in TCustomGrid, public in TCustomDrawGrid and descendants. Normally when user click on a point over empty space after cells (for example if grid has three rows but user clicks on an imaginary fourth row), the currently focused cell will move to the nearest cell to the point just clicked. We call this an outbound event. The default value is true as this has been grid's behaviour since begining. This property was added to simulate Delphi behaviour where outbound events are not available, so to enable Delphi compatibility set this property to false.
114114
115115#### property Columns
116116
117117Lazarus includes the property **columns** in TStringGrid and TDrawGrid grids. This property adds what we call custom columns. Custom columns are a collection of objects that hold properties that apply to whole columns, for example column titles (for a StringGrid it will override the value specified in the corresponding Cells[ColumnIndex, RowTitle] property), text alignment, background color, preferred editor, etc.
118118
119119Custom columns add extra properties or replace default property values on normal grid columns. Not only this, the **grid.ColCount** value may increase or decrease in order to account for the number of custom columns being attached to the grid. At this point, this means that **grid.ColCount** = **grid.FixedCols** \+ **grid.Columns.Count**.
120120
121121For example, if to a base grid with ColCount := 5 and FixedCols := 2 we:
122122
123123 * Add 3 custom columns, the base grid will be exactly as before, 2 fixed cols and 3 normal cols.
124124 * Add 1 custom column, the base grid will have ColCount := 3, that is 2 fixed cols and 1 normal cols.
125125 * Add 4 custom columns, the base grid will have ColCount := 6, that is 2 fixed cols and 4 normal cols.
126126
127127From this we can conclude that:
128128
129129 * Fixed column properties or count are not enhanced or modified respectively by custom columns.
130130 * **grid.ColCount** is usually different from **grid.Columns.Count** (**grid.ColCount** =**grid.Columns.Count** only when **FixedCols** =0)
131131
132132At design time the user can access the **columns** property in the Object Inspector to bring up the columns editor. From there you can add, remove or modify custom columns. The editor shows a list of current custom columns; by selecting items in this list the object inspector gets filled with the properties available for each column. The list of custom columns is also available in the Object Inspector component tree view, where columns can be added, deleted or modified. They appear on a lower level under the container grid.
133133
134134At runtime, columns can be modified with code like this:
135135```
136136var
137137 c: TGridColumn;
138138 begin
139139 // add a custom column a grid
140140 c := Grid.Columns.Add;
141141 // modify
142142 c.title.caption := 'Price'; // Set columns caption
143143 c.align := taRightJustify; // Align column content to the right
144144 c.color := clMoneyGreen; // Change default color to clMoneyGreen
145145 c.Index := 0; // Make it the first column
146146 // access an existing column
147147 grid.columns[0].Width := 60; // Change column 0 width to 60 pixels
148148 // delete an existing column
149149 grid.columns.delete(0); // Delete column 0
150150 ....
151151 end;
152152```
153153
154154Additionally, when using custom columns, the grids do not allow direct modification of **grids.colcount** ; adding or removing columns should be done using the **columns** property. The explanation is that there is an inconsistency on gradually removing custom columns using **ColCount** , when **ColCount** reaches **FixedCols** , the grid has no more custom columns. If we now increase **ColCount** , the new column will not be a custom column but a normal column.
155155
156156Currently there are no plans to make the grids use only custom columns.
157157
158158#### property AutoFillColumns
159159
160160Often the grids are wider than the horizontal space needed by the columns -- this leaves an unpleasant empty area at the right of the last column. The LCL grids provide a mechanism to expand the width of specified columns such that the empty space is filled automatically.
161161
162162For this purpose, **Columns** must be added either as described above, or by setting the `ColCount` property, and the grid property `AutoFillColumns` must be set to `true`. Each Column has a property `SizePriority`. When it has the value `0` the column width is taken from the `Width` property of the column. But when it has a non-zero value the column width is adjusted to the average available size remaining for all columns with a non-zero `SizePriority`.
163163
164164#### procedure SaveToFile(AFileName: String);
165165
166166Saves the grid to an XML file. The property `SaveOptions` determines what exactly is to be saved:
167167```
168168type
169169 TGridSaveOptions = (
170170 soDesign, // Save grid structure (col/row count and Options)
171171 soAttributes, // Save grid attributes (Font, Brush, TextStyle)
172172 soContent, // Save Grid Content (Text in StringGrid)
173173 soPosition // Save Grid cursor and selection position
174174 );
175175 TSaveOptions = set of TGridSaveOptions;
176176```
177177
178178#### procedure LoadFromFile(AFileName: String);
179179
180180Loads the XML file created by `SaveToFile`. Again, the `SaveOptions` define what exactly is to be loaded from the file.
181181
182182### TCustomDBGrid
183183
184184TCustomDBGrid is the base for TDBGrid.
185185
186186They do not expose Col and Row properties. To go to a certain column, use e.g. the SelectedIndex property.
187187
188188An interesting public method is AutoAdjustColumns.
189189
190190#### procedure AutoAdjustColumns
191191
192192This procedure sets the column width to the size of the widest text it finds. It can be used after loading a dataset/setting it Active.
193193
194194However, contrary to TCustomStringGrid.AutoAdjustColumns (see below), this will set your columns very wide unless you have the property dgAutoSizeColumns enabled.
195195
196196#### procedure InplaceEditor
197197
198198See example from bug 23103 - _and insert explanation of what it does/why it is needed. Validate input values? Change what is shown?_
199199```
200200procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: char);
201201 var
202202 S: String;
203203 begin
204204 if (Key in [',','.']) then
205205 begin
206206 //unlike Delphi not all InPlaceEditors are editors for string type, so check!
207207 if (DBGrid1.InplaceEditor is TStringCellEditor) then
208208 begin
209209 S := TStringCellEditor(DBGrid1.InplaceEditor).EditText;
210210 if Pos(',',S) > 0 then
211211 Key := #0
212212 else
213213 Key := ',';
214214 end;
215215 end;
216216 end;
217217```
218218
219219### TCustomStringGrid
220220
221221TCustomStringGrid serves as the base for TStringGrid. It can be used for derived TStringGrid components that want to hide published properties. See [[new intermediate grids]] for more information.
222222
223223The following properties or methods are public and are also available to TStringGrid.
224224
225225See the full [TCustomStringGrid Reference](<http://lazarus-ccr.sourceforge.net/docs/lcl/grids/tcustomstringgrid.html> "doc:lcl/grids/tcustomstringgrid.html")
226226
227227#### procedure AutoSizeColumn(aCol: Integer);
228228
229229This procedure sets the column width to the size of the widest text it finds in all rows for the column aCol. Tip: see the goDblClickAutoSize option to allow columns to be automatically resized when doubleClicking the column border.
230230
231231#### procedure AutoAdjustColumns or AutoSizeColumns
232232
233233Automatically resizes all columns by adjusting them to fit in the longest text in each column. This is a quick method of applying AutoSizeColumn() for every column in the grid.
234234
235235#### procedure Clean; overload;
236236
237237Cleans all cells in the grid, fixed or not.
238238
239239#### procedure Clean(CleanOptions: TGridZoneSet); overload;
240240
241241Cleans all cells in the grid subject to the given CleanOptions. See [TGridZoneSet](<http://lazarus-ccr.sourceforge.net/docs/lcl/grids/tgridzoneset.html> "doc:lcl/grids/tgridzoneset.html") for more information. Some examples:
242242
243243 * Clean all cells: grid.Clean([]); (the same as grid.clean)
244244 * Clean all non fixed cells: grid.Clean([gzNormal]);
245245 * Clean all cells but don't touch grid column headers: Grid.Clean([gzNormal, gzFixedRows]);
246246
247247#### procedure Clean(StartCol,StartRow,EndCol,EndRow: integer; CleanOptions:TGridZoneSet); overload;
248248
249249does the same as Clean(CleanOptions:TGridZoneSet) but restricted to the given StartCol,StartRow,EndCol and EndRow. Examples:
250250
251251 * Clean column index 4 to 6 but don't touch grid column headers: many variations, Grid.Clean(4,Grid.FixedRows,6,Grid.RowCount-1,[]); Grid.Clean(4,0,6,Grid,RowCount-1, [gzNormal]); etc.
252252
253253#### procedure Clean(aRect: TRect; CleanOptions: TGridZoneSet); overload;
254254
255255The same as Clean(StartCol,StartRow,EndCol,EndRow, CleanOptions), just taking a TRect instead of individual cell coordinates. Useful to clean the selection: grid.Clean(Grid.Selection,[]);
256256
257257#### procedure SaveToCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true);
258258
259259Save grid content to a comma separated values format (CSV) file (added in Lazarus r32179).
260260
261261The AFilename argument specifies a file name where the content will be saved. If the file exists, the content will be overwritten. If it doesn't exist, the file will be created.
262262
263263ADelimiter (an optional argument) is used to supply a custom separator if required. By default a CSV format is produced (that is, ADelimiter:=',';) for a TAB separated file ADelimiter should be #9.
264264
265265The WithHeader parameter is used to decide if a "row header" should be included or not. The row header is a list of field names at the beginning of the output file; its content comes from the last fixed row in the grid. There is an exception to this rule: if the grid has custom columns, the row header content comes from the custom column titles and not from fixed row cell content.
266266
267267If WithHeader is true and the grid does not include a fixed row or custom columns, the row header content will be taken from the first row in the grid.
268268
269269Normal CSV data output will start at the first non-fixed row in the grid.
270270
271271#### procedure LoadFromCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true);
272272
273273Loads grid content from a comma separated values format (CSV) file (added in Lazarus r32179).
274274
275275Columns will be added or deleted to or from the grid as needed according to the number of fields included in each line of the CSV file. Note that, in case of an empty (newly created) grid, columns will be added only if at least a single column exists already. Loading a CSV file will not modify the number of fixed rows that already existed in the grid.
276276
277277The _AFileName_ argument specifies the name of the source file with the CSV content.
278278
279279_ADelimiter_ optional parameter may be used to specify a different separator or delimiter. An example: for a tab-separated file, _ADelimiter_ should be #9. Another popular file format is semicolon-delimited file, where _ADelimiter_ should be ;
280280
281281The _WithHeader_ parameter is used to decide if the first row in the CSV file should be considered as the "header row" or not. If the grid has fixed rows and _WithHeader_ is true, the column captions for the last fixed row will be taken from the header row. Note however that if the grid has custom columns, the header row will be used as source for the column titles and custom column titles are always shown in the grid's first fixed row or hidden if there are no fixed rows in the grid.
282282
283283If the _LoadFromCSVFile_ procedure has difficulty loading your CSV file (e.g. quotes or spaces being incorrectly interpreted), you could manually load the grid using e.g. [[CsvDocument]]... and of course a patch for _LoadFromCSVFile_ is always welcome.
284284
285285#### property Cols[index: Integer]: TStrings read GetCols write SetCols;
286286
287287Get/set a list of strings from/to the given grid's column index starting from row index 0 to RowCount-1.
288288
289289##### Examples
290290
291291 * Set Example: Set the content of the third column in the grid from a ListBox:
292292
293293```
294294Grid.Cols[2] := ListBox1.Items;
295295```
296296
297297 * Get Example: Set the content of a Listbox from the grid's column index 4:
298298
299299```
300300procedure TForm1.FillListBox1;
301301 var
302302 StrTempList: TStringList;
303303 begin
304304 StrTempList := TStringList(Grid.Cols[4]);
305305 if StrTempList<>nil then begin
306306 ListBox1.Items.Assign(StrTempList);
307307 StrTempList.Free;
308308 end;
309309 end;
310310```
311311
312312##### Notes.
313313
314314This property works differently in Lazarus and in Delphi when getting the data from the grid. In Lazarus a temporary TStringList object is created for retrieving the column content. It is the responsibility of the user to free this object after use.
315315
316316This means also that changes in the returned list will not affect the grids content or layout.
317317
318318See the Get Example.
319319
320320#### property Rows[index: Integer]: TStrings read GetRows write SetRows;
321321
322322Get/set a list of strings from/to the given grid's row index starting from column index 0 to column ColCount-1.
323323
324324##### Notes.
325325
326326This property works differently in Lazarus and in Delphi when getting the data from the grid. In Lazarus a temporary TStringList object is created for retrieving the row content. It is the responsibility of the user to free this object after use.
327327
328328This means also that changes in the returned list will not affect the grid's content or layout.
329329
330330##### Examples
331331
332332 * Set Example: Set the content of the third row in the grid from a ListBox:
333333
334334```
335335Grid.Rows[2] := ListBox1.Items;
336336```
337337
338338 * Get Example: Set the content of a Listbox from the grid's row index 4:
339339
340340```
341341procedure TForm1.FillListBox1;
342342 var
343343 StrTempList: TStringList;
344344 begin
345345 StrTempList := TStringList(Grid.Rows[4]);
346346 if StrTempList<>nil then begin
347347 ListBox1.Items.Assign(StrTempList);
348348 StrTempList.Free;
349349 end;
350350 end;
351351```
352352
353353 * An Example that doesn't work, and its Fix: Retrieved string list is read only
354354
355355```
356356// this will not work and will cause memory leak
357357 // because returned StringList is not being freed
358358 Grid.Rows[1].CommaText := '1,2,3,4,5';
359359 Grid.Rows[2].Text := 'a'+#13#10+'s'+#13#10+'d'+#13#10+'f'+#13#10+'g';
360360
361361 // fixing the first case
362362 Lst:=TStringList.Create;
363363 Lst.CommaText := '1,2,3,4,5';
364364 Grid.Rows[1] := Lst;
365365 Lst.Free;
366366```
367367
368368#### property UseXORFeatures;
369369
370370Boolean property, default value: False;
371371
372372This property controls how the dotted focus rectangle appears in the grid. When True, the rectangle is painted using the XOR raster operation. This allow us to see the focus rectangle no matter what the cells' background color is. When False, the user can control the color of the dotted focus rectangle using the [[FocusColor property]]
373373
374374It also controls the look of the column/row resizing. When True, a line shows visually the size that the the column or row will have if the user ends the operation. When False, the column or row resizing takes effect just as the user drags the mouse.
375375
376376### TValueListEditor
377377
378378[[TValueListEditor]] is a control derived from TCustomStringGrid for editing Key-Value pairs.
379379
380380#### Property DisplayOptions
381381
382-Controls various aspects of the TValueListEditor's appearance.
382+Controls various aspects of the TValueListEditor's appearance by means of a set of the following elements:
383383
384+ * `**doColumnTitles**` : Displays column titles
385+ * `**doAutoColResize**` : Resizes column widths such that the entire client width is filled
386+ * `**doKeyColFixed**` : Keeps the width of the 1st column fixed when column widths are resized. Otherwise both columns have the same width. Requires `doAutoColSResize` to be included in the `DisplayOptions`.
387+
384388#### Property TitleCaptions
385389
386390Sets the values of the title captions (if doColumnTitles is in DisplayOptions). If DisplayOptions lacks the value doColumnTitles then default captions are used.
387391
388392#### Property Strings
389393
390394Provides access to the list of strings that hold the Key-Value pairs.
391395Key-Value pairs must be in the form:
392396'KeyName=Value'
393397
394398#### Property ItemProps
395399
396400You can use this property to control how the items in the "Value" columns can be edited.
397401This is controlled by setting the ItemProp's EditStyle and ReadOnly properties.
398402
399403#### Property KeyOptions
400404
401405KeyOptions is a set of TKeyOptions controlling whether the user can modify the contents of the "Key" column.
402406
403407 * KeyEdit: the user can edit the name of the Key
404408 * KeyAdd: the user can add keys (by pressing Insert in the grid). KeyAdd requires KeyEdit.
405409 * KeyDelete: the user can delete Key-Value pairs (by pressing Ctrl+Delete).
406410 * KeyUnique: if set, then Keys must have unique names. Attempting to enter a dulpicate Key will raise an exception.
407411
408412#### Property DropDownRows
409413
410414If the cell editor is a picklist (ValueEdit1.ItemProps['key1'].EditStyle=esPickList) this property sets the DropDownCount of the displayed list. The default is 8.
411415
412416#### Function DeleteRow
413417
414418Deletes the Key-Value pair of the indexed row removing the row entirely.
415419
416420#### Function InsertRow
417421
418422Inserts a row in the grid and sets the Key-Value pair. Returns the index of the newly inserted row.
419423
420424#### Function IsEmptyRow
421425
422426Returns true if the indexed row's cells are empty (Keys[aRow]=_; Values[aRow]=_).
423427
424428#### Function FindRow
425429
426430Returns the row that has the specified key name.
427431
428432#### Function RestoreCurrentRow
429433
430434Undoes the editing in the current row (if the editor is still focused). Happens when the user presses the Escape key.
431435
432436#### Altered behaviour of some properties derived from TCustomStringGrid
433437
434438##### Property Options
435439
436440Due to the nature of TValueListEditor its Options property has certain restrictions
437441
438442 * goColMoving is not allowed in Options (you cannot set it).
439443 * goAutoAddRows can only be set if KeyAdd is in KeyOptions. Setting KeyAdd will automatically set goAutoAddRows.
440444 * goAutoAddRowsSkipContentCheck is not allowed (for the time being, it causes a crash in TValueListeditor: needs fixing).
441445
442446##### Property FixedRows
443447
444448Can only be 1 (show column titles) or 0 (don't show column titles).
445449
446450##### Property ColCount
447451
448452Is always 2.
449453
450454##### Property Objects
451455
452456It is not recommended to use this property at all.
453457When some (internal) code manipulates the Strings property, changes are Objects will be lost.
454458
455459#### General comments on the use of TValueListEditor
456460
457461When manipulating the contents of the ValueListEditor (the grid), you can manipulate the underlying Strings property directly.
458462Be aware however, that is you do this, you **must** hide the editor, otherwise the Strings may get out of sync with the contents of the grid you will see on the screen.
459463
460464If you want to insert or delete rows etc. then the preferred way is to use the public methods from TValueListEditor: DeleteRow(), InsertRow(), MoveRow(), ExchangeRow() or the various Sort() methods.
461465
462466Trying to use ancestor's methods to manipulate rows or columns (e.g. Columns.Add) might result in a crash.
463467
464468#### Saving and loading the contenst of a TValueListEditor
465469
466470##### Using the Strings property
467471
468472If you just want to save and load the content of the TValueListEditor (e.g. the Key/Value pairs, not the layout) you can use Strings.SaveToFile and Strings.LoadFromFile.
469473In case of LoadFromFile the `RowCount` property will be autmatically adjusted.
470474No sanity checks are performed upon LoadFromFile (so lines that do not represent a Key/Value pair will end up as Keys without Value).
471475
472476##### Using TValueList.SaveToFile and TValueList.LoadFromFile
473477
474478When using SaveToFile and LoadFromFile you get the additional benefits of also being able to save and load the layout, like with other grids.
475479SaveToFile will also save information about wether or not the TValueListEditor uses ColumnTitles, and if so, it saves them.
476480
477481In contrast to it's ancestors TValueList.LoadFromFile performs sanity checks on the file it tries to read. In particular RowCount must be specified and there cannot be a cell with RowIndex > RowCount or ColumnIndex other than 0 or 1.
478482If the file does not seem to be a valid TValueListEditor grid file, and exception is raised.
479483
480484TValueList.SaveToFile and TValueList.LoadFromFile do not work correctly in Lazarus versions <= 2.1 r62044.
481485
482486##### Using SaveToCSVFile and LoadFromCSV methods
483487
484488These methods should **not** be used at the moment, since for a TValueListEditor they are flawed.
485489
486490## Working with grids
487491
488492### Customizing grids
489493
490494Grid are components derived from the [TCustomControl](<http://lazarus-ccr.sourceforge.net/docs/lcl/controls/tcustomcontrol.html>) class, and don't have a native widget associated with them which means that grids are not restricted by the look of current interface theme. This can be both an advantage and a disadvantage: usually programmers want to create a uniform-look application. The good news is that Lazarus grids are flexible enough to get something from both worlds; programmers can easily make grids look similar to other native controls, or they can customize the grid to the finest detail so they can obtain almost the same look in any platform or widget interface (that is, with the exception of scrollbars, because their look is still determined by the current theme).
491495
492496### Properties and Events for customizing grids
493497
494498Some properties can affect the way the grid looks by acting when the cell is about to be painted in PrepareCanvas/OnPrepareCanvas by changing default canvas properties like brush color or font. Following is a list of such properties:
495499
496500 * **AlternateColor.** With this the user can change the background color appears on alternated rows. This is to allow easy reading off of grid rows data.
497501 * **Color.** This sets the primary color used to draw non fixed cells background.
498502 * **FixedColor.** This is the color used to draw fixed cells background.
499503 * **Flat.** This eliminates the 3d look of fixed cells.
500504
501505 * **TitleFont.** Font used to draw the text in fixed cells.
502506
503507 * **TitleStyle.** This property changes the 3D look of fixed cells, there are 3 settings:
504508 * _tsLazarus._ This is the default look
505509 * _tsNative._ This tries to set a look that is conforms with the current widgetset theme.
506510 * _tsStandard._ This style is a more contrasted look, like Delphi grids.
507511 * **AltColorStartNormal.** Boolean. If true: alternate color is always in the second row after fixed rows, the first row after fixed rows will be always color. If false: default color is set to the first row as if there were no fixed rows.
508512 * **BorderColor.** This sets the grid's border color used when Flat:=True and BorderStyle:=bsSingle;
509513 * **EditorBorderStyle.** If set to bsNone under Windows the cell editors will not have the border, like in delphi, set to bsSingle by default because the border can be theme specific in some widgetsets and to allow a uniform look.
510514
511515 * **FocusColor.** The color used to draw the current focused cell if UseXORFeatures is not set, by default this is clRed.
512516 * **FocusRectVisible.** Turns on/off the drawing of focused cell.
513517 * **GridLineColor.** Color of grid lines in non fixed area.
514518 * **GridLineStyle.** Pen style used to draw lines in non fixed area, possible choices are: _psSolid_ , _psDash_ , _psDot_ , _psDashDot_ , _psDashDotDot_ , _psinsideFrame_ , _psPattern_ ,_psClear_. Default is _psSolid_.
515519 * **SelectedColor.** Color used to draw cell background on selected cells.
516520 * **UseXORFeatures.** If set, focus rect is drawn using XOR mode so it should make visible the focus rect in combination with any cell color ackground. It also affects the moving columns look.
517521 * **DefaultDrawing.** Boolean. Normally the grids prepare the grid canvas using some properties according to the kind of cell that is being painted. If the user writes an OnDrawCell event handler, a set DefaultDrawing also paints the cell background. If the user draws the cell himself, it is better to turn off this property so painting is not duplicated. In a StringGrid, a set DefaultDrawing draws the text in each cell.
518522 * **AutoAdvance.** Where the cell cursor will go when pressing Enter, or after editing.
519523 * **TabAdvance.** Where the cell cursor will go when pressing Tab or Shift-Tab.
520524 * **ExtendedColSizing.** If true user can resize columns not just at the headers but along the columns height.
521525
522526Other properties that also affect the grids look.
523527
524528**Options**.
525529
526530Options property is a set with some elements to enable diverse functionality but some are related directly with grid's look. This options can be set at designtime or runtime.
527531
528532 * **goFixedVertLine, goFixedHorzLine** it draws a vertical or horizontal line respectively delimiting cells or columns in fixed area, active by default.
529533 * **goVertLine, goHorzLine** the same as previous, but for normal browseable area. A grid can be made to simulate a listbox by unsetting both of this elements.
530534 * **goDrawFocusSelected** if this element is enabled a selection background is painted in focused cell in addition to focused dotted rectangle (note this doesn't work yet when goRowSelect option is set, in such case row is always painted as if goDrawFocusSelected is set)
531535 * **goRowSelect** select the full row instead of individual cells
532536 * **goFixedRowNumbering** if set, grid will do numbering of rows in first fixed column
533537 * **goHeaderHotTracking** if set, the grid will try to show a different look when the mouse cursor is overing any fixed cell. In order for this to work, desired cell zone needs to be enabled with property HeaderHotZones. Try combining this option with property TitleStyle:=tsNative to get themed hot tracking look.
534538 * **goHeaderPushedLook** if set, this element enables a pushed look when clicking any fixed cell. The zone of "pushable" cells is enabled using HeaderPusedZones property.
535539
536540(write more)
537541
538542### Description of grid's drawing process
539543
540544Like other custom controls, the grid is drawn using the paint method. In general terms the grid is drawn by painting all rows, and each row by painting its individual cells.
541545
542546The process is as follow:
543547
544548 * First the visible cells area is determined: each row is tested to see if it intersects the canvas clipping region; if it's ok, then the visible area is painted by drawing columns of each row.
545549 * The column and row values are used to identify the cell that is about to be painted and again each column is tested for intersection with the clippling region; if everything is ok, some additional properties like the cell's rectangular extent and visual state are passed as arguments to the DrawCell method.
546550 * As the drawing process is running, the visual state of each cell is adjusted according to grid options and position within grid. The visual state is retained in a varible of type TGridDrawState which is a set with following elements:
547551 * _gdSelected_ The cell will have a selected look.
548552 * _gdFocused_ The cell will have a focused look.
549553 * _gdFixed_ Cell have to be painted with fixed cell look.
550554 * _gdHot_ the mouse is over this cell, so paint it with hot tracking look
551555 * _gdPushed_ the cell is being clicked, paint it with pushed look
552556 * **DrawCell.** The DrawCell method is virtual and may be overriden in descendant grids to do custom drawing. The information passed to DrawCell helps to identify the particular cell is being painted, the physical area ocuppied in screen and its visible status. See DrawCell reference for details. For each cell the following occurs:
553557 * **PrepareCanvas.** In this method, if the DefaultDrawing property is set, the grid canvas is setup with default properties for brush and font based on current visual state. For several design and runtime properties, the text alignment is set to match programmer selection in custom columns if they exists. If DefaultDrawing is false, brush color is set to clWindow and Font color to clWindowText, the text alignment is set with grids defaultTextStyle property value.
554558 * **OnPrepareCanvas.** If the programmer wrote an event handler for OnPrepareCanvas event, it is called at this point. This event can be used for doing simple customization like changing cell's background color, font's properties like color, fontface and style, Text layout like different combinations of left, center, top, bottom, right alignment, etc. Any change made to the canvas in this event would be lost, because the next cell drawing will reset canvas again to a default state. So it's safe doing changes only for particular cell or cells and forget about it for the rest. Using this event sometimes helps to avoid using the OnDrawCell grid event, where users would be forced to duplicate the grid's drawing code. Todo: samples of what can be made and what to leave for OnDrawCell?...
555559 * **OnDrawCell.** Next if no handler for OnDrawCell event was specified, the grid calls the DefaultDrawCell method which simply paints the cell background using the current canvas brush color and style. If the OnDrawCell handler exists, the grid first paints the cell background but only if DefaultDrawing property was set, then it calls OnDrawCell event to do custom cell painting. Usually programmers want to do custom drawing only for particular cells, but standard drawing for others; in this case, they can restrict custom operation to certain cell or cells by looking into ACol, ARow and AState arguments, and for other cells simply call DefaultDrawCell method and let the grid to take care of it.
556560 * **Text.** At this point (only for TStringGrid) if DefaultDrawing property is true, the cell's text content is painted.
557561 * **Grid lines**. The last step for each cell is to paint the grid lines: if grid options goVertLine, goHorzLine, goFixedVertLine and goFixedHorzLine are specified the cell grid is drawn at this point. Grids with only rows or only cols can be obtained by changing these options. If the programmer elected to have a "themed" look it is done at this point also (see property TitleStyle).
558562 * **FocusRect.** When all columns of current row have been painted it is time to draw the focus rectangle for the current selected cell or for the whole row if goRowSelect option is set.
559563
560564#### Differences with Delphi
561565
562566 * In Lazarus TCustomGrid.DrawCell method is not abstract and its default implementation does basic cell background filling.
563567 * In Delphi, the cell's text is drawn before entering the OnDrawCell event (see [bug report #9619](<http://www.freepascal.org/mantis/view.php?id=9619>)).
564568
565569### Grid's cell selection
566570
567571The location of a grid's current (focused) cell (or row) can be changed using keyboard, mouse or through code. In order to change cell focus successfully to another position, we must test the target position to see if it is allowed to receive cell focus. When using keyboard, the property AutoAdvance performs part of the process by finding what should be the next focused cell. When using mouse clicks or moving by code, focus will not move from the current cell unless the target cell is permitted to receive focus.
568572
569573The grid calls function SelectCell to see if a cell is focusable: if this function returns true, then the target cell identified with arguments aCol and aRow is focusable (the current implementation of TCustomGrid simply returns true). TCustomDrawGrid and hence TDrawGrid and TStringGrid override this method to check first if cell is any wider than 0; normally you don't want a 0 width cell selected so a cell with these characteristics is skipped automatically in the process of finding a suitable cell. The other thing the overridden method SelectCell does is to call the user configurable event OnSelectCell: this event receives the cell coordinates as arguments and always returns a default result value of true.
570574
571575Once a cell is known to be focusable and we are sure a movement will take place, first the method BeforeMoveSelection is called; this in turns triggers the OnBeforeSelection event. This method's arguments are the coordinates for the new focused cell. At this point any visible editor is hidden too. The "before" word means that selection is not yet changed and current focused coordinates can be accessed with grid.Col and grid.Row properties.
572576
573577After that, the internal focused cell coordinates are changed and then MoveSelection method is called; this method's purpose is to trigger the OnSelection event if set (this is a notification that the focused cell has, by this time, already changed and cell coordinates are now available through grid.row and grid.col properties).
574578
575579Note that is not good to use OnSelectCell event to detect cell focus changes, as this event will be triggered several times even for the same cell in the process of finding a suitable cell. Is better to use OnBeforeSelection or OnSelection events for this purpose.
576580
577581#### Differences with Delphi
578582
579583 * SelectCell and OnSelectCell behaviour is probably different - can't really comment on the differences. In Lazarus they are used in functionality like AutoAdvance which as far as I know doesn't exist in Delphi.
580584
581585### When built-in properties are not enough: derived grids
582586
583587Derived grids usually have to override the following methods:
584588DrawAllRows: Draws all visible rows.
585589DrawRow: Draws all cells in a row.
586590DrawRow draws all cells in the row by first checking if cell is within clipping region, and only draws the cell if it is.
587591DrawCell:
588592DrawCellGrid:
589593DrawCellText:
590594DrawFocusRect:
591595(write me).
592596
593597### What happens in the `TCustomGrid.Paint` method?
594598
595599The following list is to show the internal order of method calls for painting a `TCustomGrid` (or descendant). Every item in the lists represents method calls during the paint operation. This ought to help finding the right point to change behaviour when it comes to descending from `TCustomGrid`.
596600
597601 * **`DrawEdges`** : Draws the grid's outer border.
598602 * **`DrawAllRows`** (virtual): Draws all rows in the visible part of the grid. This is the only method called in the painting process that is declared _`virtual`_.
599603 * **`DrawRow`** (virtual): Is called for each row inside the current viewport.
600604 * **`DrawCell`** (virtual): Is first called for each 'normal' (i.e. not fixed) cell in the row.
601605 * **`PrepareCanvas`** (virtual): Sets the canvas's drawing styles according to the current cell's visual properties.
602606 * **`DrawFillRect`** : Draws the cell's background with the styles set in `PrepareCanvas`.
603607 * **`DrawCellGrid`** (virtual): Draws the cell's border lines.
604608 * **`DrawFocusRect`** (virtual): In `TCustomGrid` this method does nothing. In descendent grids this method is used to draw the focus rectangle inside the active cell.
605609 * **`DrawCell`** (virtual): (see above) Is called for each fixed cell in the row's visible viewport.
606610 * **`DrawColRowMoving`** : only active while moving a column or row. This method draws the line that indicates the row's/column's new position.
607611 * **`DrawBorder`** : If needed (`Flat=TRUE` and `BorderStyle=bsSingle`), this draws an inner border line.
608612
609613#### Additional methods to draw in `TCustomGrid`
610614
611615These methods are declared and (partly) implemented in `TCustomGrid`, but they are not called directly from here. They are used by descendant classes to draw the cell's content.
612616
613617 * **`DrawCellText`** (virtual): Writes/draws the text that is passes as parameter into the cell. The text is formatted by using the styles that are active in `Canvas.TextStyle` (see also `PrepareCanvas`).
614618 * **`DrawThemedCell`** (virtual): Is only used for fixed cells and if `TitleStyle=tsNative`. It draws the cell's background using `ThemeServices`.
615619 * **`DrawColumnText`** (virtual): Is only used for column header cells.
616620 * **`DrawColumnTitleImage`** : If the grid as an assigned `TitleImageList` and `Columns.Title[x].Title.ImageIndex` contains a valid value, that image is drawn into the cell.
617621 * **`DrawCellText`** : (see above)
618622 * **`DrawTextInCell`** (virtual): Is used for 'normal' (i.e. not fixed) cells.In `TCustomGrid` this method does nothing. In descendent grids this method is used to draw the cells content. In `TStringGrid` theis method calls `DrawCellText` (see above).
619623
620624#### Drawing methods introduced by `TCustomDrawGrid`
621625
622626As you can see the base class `TCustomGrid` does not draw any content into the cells. This is done in **`TCustomDrawGrid`**. **`TCustomDrawGrid`** overrides the method `DrawCell` in the following way:
623627
624628 * **`DrawCell`** (override):
625629 * **`PrepareCanvas`** : calls the inherited method from **`TCustomGrid`**.
626630 * **`DefaultDrawCell`** (virtual): This method is only called if the event handler for `OnDrawCell` is NOT assigned.
627631 * **`DrawFillRect / DrawThemedCell`** (von `TCustomGrid`): Draws the cell's background. If `TitleStyle=tsNative`, the background of fixed cells is drawn using `DrawThemedCell`.
628632 * **`DrawCellAutonumbering`** (virtual): If `goFixedAutonumbering in Options`, row numbers are drawn into the first fixed column using `TCustomGrid.DrawCellText`.
629633 * **`DrawColumnText`** (from `TCustomGrid`): Is only called for column header cells (see _Additional methods to draw in` TCustomGrid`_).
630634 * **`DrawTextInCell`** (from `TCustomGrid`): Is only called for 'normal' (i.e. not fixed) cells (see _Additional methods to draw in` TCustomGrid`_).
631635 * **`DrawCellGrid`** (virtual): Draws the cell's borders
632636
633637### Save and Retrieve Grid Content
634638
635639The **SaveToFile** and **LoadFromFile** methods allows a grid to save and retrieve it's layout and data to/from a XML format file. TStringGrid, as inherited from TCustomStringGrid, has also the ability to "export" and "import" its content to/from a Comma Separated Values format file, best known as CSV files. This is described in the **SaveToCSVFile** and **LoadFromCSVFile** methods reference (TODO: make links).
636640
637641The kind of information that can be saved and then retrieved when using **SaveToFile** and **LoadFromFile** is determined by the SaveOptions property (of type TSaveOptions) which is a set of options described as follows:
638642```
639643soDesign: Save & load ColCount,RowCount,FixedCols,FixedRows,
640644 ColWidths, RowHeights and Options (TCustomGrid)
641645 soPosition: Save & load Scroll position, Row, Col and Selection (TCustomGrid)
642646 soAttributes: Save & load Colors, Text Alignment & Layout, etc. (TCustomDrawGrid)
643647 soContent: Save & load Text (TCustomStringGrid)
644648 soAll: set of all options (soAll:=[soDesign,soPosition,soAttributes,soContent];)
645649```
646650
647651Not all options apply to all kind of grids, for example, soContent do not apply to TCustomGrid derived grids like TDrawGrid as this kind of grid does not have the concept of "content". TStringGrid is a special kind of grid that "knows" how to handle strings and can use the soContent option if specified.
648652
649653The soAttributes option is not used in Lazarus standard grids, is there for derived grids.
650654
651655When using **LoadFromFile** the grid also uses the SaveOptions property in order to know what kind of information needs to retrieve from the file, so is perfectly possible to specify SaveOptions:=[soDesign,soContent] on saving and only SaveOptions:=[soContent] on loading.
652656
653657For a TStringGrid the default SaveOptions value is [soContent], for other kind of grids, SaveOptions is the empty set.
654658
655659**Note:** One common issue when saving & retrieving grid data occurs when the user specify the SaveOptions property before **SaveToFile** but not before **LoadFromFile**. When using **LoadFromFile** some time after **SaveToFile** have been used, the SaveOptions property is properly set, but if **LoadFromFile** is executed on next program run, the SaveOptions property might not have been properly setup, for this reason is recommended to always specify SaveOptions property just before **LoadFromFile** , or doing it globally at program start like in the following example:
656660
657661* * *
658662
659663**Example:**
660664
661665 1. First, go to menu "File -> New -> Application";
662666 2. Put an empty TStringGrid on the form;
663667 3. Put a TButton and TOpenDialog on the form;
664668 4. Add the event OnCreate for the form;
665669 5. Add the event OnClick for the button.
666670
667671```
668672unit Unit1;
669673
670674 {$mode objfpc}{$H+}
671675
672676 interface
673677
674678 uses
675679 Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Grids,
676680 Buttons, StdCtrls, XMLCfg;
677681
678682 type
679683
680684 { TForm1 }
681685 TForm1 = class(TForm)
682686 StringGrid1: TStringGrid;
683687 Button1: TButton;
684688 OpenDialog1: TOpenDialog;
685689 procedure Button1Click(Sender: TObject);
686690 procedure Form1Create(Sender: TObject);
687691 private
688692 { private declarations }
689693 public
690694 { public declarations }
691695 end;
692696
693697 var
694698 Form1: TForm1;
695699
696700 implementation
697701
698702 { TForm1 }
699703
700704 procedure TForm1.Form1Create(Sender: TObject);
701705 begin
702706 //sets the SaveOptions at creation time of the form
703707 stringgrid1.SaveOptions := [soDesign,soPosition,soAttributes,soContent];
704708 end;
705709
706710
707711 procedure TForm1.Button1Click(Sender: TObject);
708712 begin
709713 //Ask if thew Execute method of the OpenDialog was launched
710714 //when this occurs, the user selects an XML file to Load
711715 //wich name was stored in the FileName prop.
712716
713717 if opendialog1.Execute then
714718 begin
715719 //Clear the grid
716720 StringGrid1.Clear;
717721 //Load the XML
718722 StringGrid1.LoadFromFile(OpenDialog1.FileName);
719723 //Refresh the Grid
720724 StringGrid1.Refresh;
721725 end;
722726 end;
723727
724728 initialization
725729 {$I unit1.lrs}
726730
727731 end.
728732```
729733
730734* * *
731735
732736**The sample xml file:** (Copy the text below into a txt file. Don't forget put the xml header :-))
733737```
734738<?xml version="1.0"?>
735739 <CONFIG>
736740 <grid version="3">
737741 <saveoptions create="True" position="True" content="True"/>
738742 <design columncount="2" rowcount="5" fixedcols="1" fixedrows="1" defaultcolwidth="64" defaultRowHeight="20">
739743 <options>
740744 <goFixedVertLine value="True"/>
741745 <goFixedHorzLine value="True"/>
742746 <goVertLine value="True"/>
743747 <goHorzLine value="True"/>
744748 <goRangeSelect value="True"/>
745749 <goDrawFocusSelected value="False"/>
746750 <goRowSizing value="False"/>
747751 <goColSizing value="False"/>
748752 <goRowMoving value="False"/>
749753 <goColMoving value="False"/>
750754 <goEditing value="False"/>
751755 <goTabs value="False"/>
752756 <goRowSelect value="False"/>
753757 <goAlwaysShowEditor value="False"/>
754758 <goThumbTracking value="False"/>
755759 <goColSpanning value="False"/>
756760 <goRelaxedRowSelect value="False"/>
757761 <goDblClickAutoSize value="False"/>
758762 <goSmoothScroll value="True"/>
759763 </options>
760764 </design>
761765 <position topleftcol="1" topleftrow="1" col="1" row="1">
762766 <selection left="1" top="1" right="1" bottom="1"/>
763767 </position>
764768 <content>
765769 <cells cellcount="10">
766770 <cell1 column="0" row="0" text="Title Col1"/>
767771 <cell2 column="0" row="1" text="value(1.1)"/>
768772 <cell3 column="0" row="2" text="value(2.1)"/>
769773 <cell4 column="0" row="3" text="value(3.1)"/>
770774 <cell5 column="0" row="4" text="value(4.1)"/>
771775 <cell6 column="1" row="0" text="Title Col2"/>
772776 <cell7 column="1" row="1" text="value(1.2)"/>
773777 <cell8 column="1" row="2" text="value(2.2)"/>
774778 <cell9 column="1" row="3" text="value(3.2)"/>
775779 <cell10 column="1" row="4" text="value(4.2)"/>
776780 </cells>
777781 </content>
778782 </grid>
779783 </CONFIG>
780784```
781785
782786### Grid Cell Editors
783787
784788The grid uses cell editors to change the content of cells.
785789
786790For a specialized grid like TStringGrid, the editor is the usual single line text editor control, but sometimes it's desirable to have other means to enter information. For example:
787791
788792 1. show the open file dialog to find the location of a file so the user doesn't have to type the full path manually
789793 2. if the text in the cell represents a date, popup a calendar so we can choose a specific date easily.
790794
791795Sometimes the information the user should enter in a cell is restricted to a limited list of words; in this case typing the information directly might introduce errors and validation routines might need to be implemented. We can avoid this by using a cell editor that presents the user with a list containing only the legal values.
792796
793797This is also the case for generic grids like TDrawGrid where the user needs some kind of structure to hold the data that will be shown in the grid. In this situation, the information that is entered in the cell editor updates the internal structure to reflect the changes in the grid.
794798
795799#### Builtin cell editors
796800
797801The grids.pas unit already includes some of the most used cell editors ready for use in grids. It is also possible to create new cell editors (custom cell editors) if the built-in editors are not appropiate for a specific task.
798802
799803The built-in cell editors are Button, Edit, and Picklist.
800804
801805#### Using cell editors
802806
803807Users can specify what editor will be used for a cell using one of two methods.
804808
805809 1. **Using a custom column and selecting the ButtonStyle property of the column**. In this method the user can select the style of the editor that will be shown. Available values are: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn.
806810 2. **Using OnSelectEditor grid event**. Here the user specifies in the Editor parameter which editor to use for a cell identified for column aCol and row ARow in a TCustomDrawGrid derived grid or TColumn in TCustomDBGrid. For this purpose there is a useful public function of grids, EditorByStyle(), that takes as parameter one of the following values: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn. This method takes precedence over the first one using custom columns. A custom cell editor can be specified here (see chapter 5 of [this document](<http://www.freepascal.org/~michael/articles/grids/grids.pdf>) by Michaël Van Canneyt which explains how to correctly implement a custom editor). This event is also the place to setup the editor with values specific to the cell, row or column (e.g. a custom popupmenu for the cell editor).
807811
808812Setting the _ButtonStyle_ property only works if a column is created with the _StringGrid1.Columns.Add;_ statement. Using expression like _StringGrid1.ColCount:=X;_ will cause an exception. Setting the ButtonStyle property can be done with a similar code:
809813```
810814if ColCB< StringGrid1.Columns.Count
811815 then StringGrid1.Columns.Items[ColCB].ButtonStyle:=cbsCheckboxColumn;
812816```
813817
814818#### Description of editor styles
815819
816820The following is a description of the editor styles. They are enumerated values of type TColumnButtonStyle and so they are prefixed by 'cbs'. This type was used to remain compatible with Delphi's DBGrid.
817821
818822 * **cbsAuto**
819823
820824This is the default editor style for TCustomGrid derived grids. The actual editor class that will be used to edit the cell content depends on several factors. For TCustomGrids it uses a TStringCellEditor class derived from TCustomMaskEdit. This editor is specialized to edit single line strings. It is then used in TStringGrid and TDrawGrid by default. When using Custom Columns, if the programmer filled the Column's PickList property, this behaves as if cbsPickList editor style was set. For a TCustomDBGrid that has a field of type boolean, it behaves as if cbsCheckBoxColumn editor style was specified. This is the recommended value for Custom Cell Editors. TODO: related OnEditingDone.
821825
822826 * **cbsEllipsis**
823827
824828This editor style is the most generic one. When used, a button appears in the editing cell and programmers could use the OnEditButtonClick grid event to detect when the user has pressed the button and take any action programmed for such a cell. For example a programmer could use this editor style to pop up a calendar dialog to allow the user easily to select a specific date. Other possibilities could be to show a file open dialog to find files, a calculator so user can enter the numeric result of calcs, etc.
825829
826830OnEditButtonClick is just a notification, to find out in which cell a button has been clicked by taking a look at the grid.Row and grid.Col properties.
827831
828832A DBGrid has specific properties to retrieve the active column or field and because this event occurs in the active record, it could update the information in the active field.
829833
830834This editor style is implemented using TButtonCellEditor, a direct descendant of TButton.
831835
832836 * **cbsNone**
833837
834838This editor style instructs the grid not to use any editor for a specific cell or column; it behaves then, as if the grid were readonly for such a cell or column.
835839
836840 * **cbsPickList**
837841
838842Used to present the user with a list of values that can be entered. This editor style is implemented using TPickListCellEditor, a component derived from TCustomComboBox. The list of values that are shown is filled in one of two ways depending on the method used to select the editor style.
839843
840844 1. When using custom columns, programmers can enter a list of values using the column's PickList property. [FOR BEGINNERS: TODO: exact procedure to edit the list]
841845 2. In OnSelectEditor, programmers get the TPickListCellEditor instance using the function EditorByStyle(cbsPickList). See here for an example
842846
843847The value in a TStringGrid grid will automatically reflect the value selected. If necessary the programmer could detect the moment the value is selected by writing an event handler for the grid's OnPickListSelect event, so additional steps can be taken (for example, to process the new value). TODO: related OnEditingDone.
844848
845849 * **cbsCheckboxColumn**
846850
847851It can be useful when the data content associated with the column is restricted to a pair of values, for example, yes-no, true-false, on-off, 1-0, etc. Instead of forcing the user to type the values for this kind of data in a StringCellEditor or to choose one from a list, cbsCheckboxColumn is used to modify the data of a column by using a checkbox representation that the user can toggle by using a mouse click or pressing the SPACE key (if the column, containing the checkbox is selected and if the StringGrid is editable).
848852
849853Getting or setting the value of the checkbox in a cell is done the following way:
850854```
851855StringGrid1.Cell[x,y]:='Z';
852856```
853857
854858where Z shall be replaced by _0_ for Unchecked, _1_ for Checked and an empty string for Grayed. Note that any value (string) different from _0_ and _1_ will be displayed as a grayed checkbox.
855859
856860If a columns' ButtonStyle property is set to cbsAuto and DBGrid detects that the field associated with the column is a boolean field, then the grid uses this editor style automatically. This automatic selection can be disabled or enabled using DBGrid's OptionsExtra property; setting dgeCheckboxColumn element to false disables this feature.
857861
858862The values that are used to recognize the checked or unchecked states are set in a column's properties ValueChecked and ValueUnchecked.
859863
860864At any moment, the field value can be in one to three states: Unchecked, Checked or Grayed. Internally these states are identified by the following values of type TDBGridCheckBoxState: gcbpUnChecked, gcbpChecked and gcbpGrayed.
861865
862866This editor style doesn't use real TCheckbox components to handle user interaction: the visual representation is given by three built-in bitmap images that corresponds to the possible states of checkbox. The used bitmaps can be customized by writing a handler for DBGrid event OnUserCheckboxBitmap; the handler of this event gets the state of the checkbox in the parameter CheckedState of type TDBGridCheckboxState and a bitmap parameter that the programmer could use to specify custom bitmaps.
863867
864868 * **cbsButtonColumn**
865869
866870This editor style is used to show a button on every cell on column. Like in the case of cbsCheckboxColumn this editor do not use real buttons, the appearance is defined by current widgetset theme.
867871
868872The user knows what particular button was pressed by handling the grid's OnEditButtonClick and checking grid's col and row. Note that in this particular case, grid's col and row do not identify the currently selected cell, but the cell of the clicked button. Once the OnEditButtonClick event has been handled, and if the user has not modified the grid's col or row in this handler, the grid automatically resets the col and row to reflect the currently selected cell. While handling the OnEditButtonClick the current grid selection is available in grid.Selection which is a TRect property, left and right represent Column indexes, Top and Bottom are row indexes.
869873
870874The button's caption is the corresponding cell string.
871875
872876### Editing grids
873877
874878The effect of editing is different depending on what kind of grid is used, for example, TStringGrid stores the edited text internally and TDBGrid affects records in a dataset. TDrawGrid doesn't know what to do with the edited text, if the programmer do not take control on it, it's simply discarded.
875879For TDrawGrid (although this should work for all grid classes) in order to access the edited text, the programmer can use the event **OnSetEditText** which is triggered every time the user modify something in the editor, the _aCol_ and _aRow_ parameters of this event identify the cell being edited, the parameter _value_ holds the text, this is the recommended method. Another way of access the edited text is taking it directly from the editor once the editing process has ended by using the event **OnEditingDone**. This could be accomplished by accessing the internal editor used for editing, in this case, the default "String Cell Editor". For this, there are two methods: The first is by using the event **OnSelectEditor** where the parameter _Editor_ is the instance which will be used to edit a cell, you have to store this instance in a variable, for example TheEditor (of type _TStringCellEditor_), for later use in **OnEditingDone**. The second alternative is using the grid's method **EditorByStyle** , this method accepts an "editor style" as parameter and it returns the instace of the editor corresponding to that style. In our case knowing that the style cbsAuto returns the default cell editor, in the **OnEditingDone** event handler we can use directly TheEditor := Grid.EditorByStyle(cbsAuto). You can then get the text with _TheEditor.Text_. If you use this method note that "with great power, comes great responsibility".
876880
877881#### Options and properties that affect editing
878882
879883The editing behavior is controlled by a number of properties and options, virtually any adjustment or options that affects editing requires an editable grid or such adjustment might be ignored.
880884At start the editable state of the grids is different, for TDrawGrid and TStringGrid editing is disabled, for TDbGrid is enabled by default.
881885The **Options** property has several items that deal with editing, they are described below:
882886
883887 * **goEditing, dgEditing (in DbGrid)**. This option changes the editable state of the grid, can be changed at runtime because it is checked when editing is about to be started.
884888 * **goAlwaysShowEditor**. Normally the editor is hidden and it becomes visible only when it's needed. With this option, the editor will be visible all the time, if grid is not editable, this option is ignored and editor is always hidden.
885889
886890### Cell hints
887891
888892In addition to the standard hint of most controls, the descendants of TCustomGrid can display a special hint for the cell over which the mouse hovers. This feature is controlled by the grid options **goCellHints** and **goTruncCellHints** (or **dgCellHints** and **gdTruncCellHints** for DBGrid, respectively):
889893
890894 * **goCellHints** evaluates the event **OnGetCellHint** to retrieve the displayed text for the cell specified by the passed parameters. In case of the TDrawGrid and the TStringGrid, column and row indexes are given, while in case of the TDBGrid the cell text can be extracted from the Field assigned to the specified column object.
891895 * **goTruncCellHints** displays a hint only if the text does not fit into a cell and is truncated; the hint text is the untruncated cell text. In Lazarus 1.9+, this text is passed to the event **OnGetCellHint** for further refinement (e.g. display the entire text of a memo in the hint). In case of the truncated cell texts, it is recommended to also activate the option **goCellEllipsis** (or **dgCellEllipsis** for DBGrid) which adds a '...' to the truncated cell text.
892896
893897If the grid has a value in its standard property **Hint** then the property **HintPriority** can be used to control how all these hint texts can be combined into a single popup window:
894898
895899 * **chpAll** puts the standard Hint into the first line, the hint determined by the OnGetCellHint event into the second line, and the hint for the truncated cells into the third line
896900 * **chpAllNoDefault** displays only the cell hints given by the OnGetCellHint event and by the truncated cells.
897901 * **chpTruncOnly** displays only hints for the truncated cells; this is the default settings.
898902
899903In addition, the standard property **ShowHint** of the grid must be set to true in order to show cell hints.
900904
901905## Grids Howto and Examples
902906
903907### Focusing a cell
904908
905909Focusing a cell in TStringGrid is easy. Note that counting starts from zero not 1. So to focus the cell in the 10th row and the 9th column, do:
906910```
907911StringGrid1.Row := 9;
908912 StringGrid1.Col := 8;
909913```
910914
911915### Example: How to set a custom cell editor
912916
913917See lazarus/examples/gridexamples/gridcelleditor/gridcelleditor.lpi (from laz 1.2)
914918
915919### Example: How to add a button editor
916920```
917921// Conditionally show button editor in column index 1 or 2 if
918922 // cell in column index 1 is empty
919923 procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer;
920924 var Editor: TWinControl);
921925 begin
922926 if (aCol = 1) or (aCol = 2) then
923927 if StringGrid1.Cells[1,aRow] = '' then
924928 begin
925929 Editor := StringGrid1.EditorByStyle(cbsEllipsis);
926930 end;
927931 end;
928932
929933 // Triggering Action ...
930934 procedure TForm1.StringGrid1EditButtonClick(Sender: TObject);
931935 begin
932936 if StringGrid1.Col = 1 then Showmessage('column 1 editor clicked');
933937 if StringGrid1.Col = 2 then Showmessage('column 2 editor clicked');
934938 end;
935939```
936940
937941### Example: Working with Picklist, How to make it read only and How to fill it at run time
938942
939943Using grid's event OnSelectEditor one can customize how PickList editor (see cbsPickList button style) behaves. In next example the picklist editor from column 1 is modified so on odd rows the user can enter values by typing, on even rows the values are limited to the ones contained in its list. Also, this example show how to fill the list with different values depending on the row being processed.
940944```
941945procedure TForm1.gridSelectEditor(Sender: TObject; aCol, aRow: Integer;
942946 var Editor: TWinControl);
943947 begin
944948 if aCol=1 then begin
945949 Editor := StringGrid1.EditorByStyle(cbsPickList);
946950 if (Editor is TCustomComboBox) then
947951 with Editor as TCustomComboBox do begin
948952 if (aRow mod 2=0) then
949953 Style := csDropDown
950954 else
951955 Style := csDropDownList;
952956 case aRow of
953957 1:
954958 Items.CommaText := 'ONE,TWO,THREE,FOUR';
955959 2:
956960 Items.CommaText := 'A,B,C,D,E';
957961 3:
958962 Items.CommaText := 'MX,ES,NL,UK';
959963 4:
960964 Items.CommaText := 'RED,GREEN,BLUE,YELLOW';
961965 end;
962966 end;
963967 end;
964968 end;
965969```
966970
967971### Aligning text in StringGrids
968972
969973This code shows how to use different text alignments in columns 2 and 3.
970974```
971975procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
972976 aState: TGridDrawState);
973977 var
974978 MyTextStyle: TTextStyle;
975979 begin
976980 if (aCol=2) or (aCol=3) then
977981 begin
978982 MyTextStyle := StringGrid1.Canvas.TextStyle;
979983 if aCol=2 then
980984 MyTextStyle.Alignment := taRightJustify
981985 else
982986 if aCol=3 then
983987 MyTextStyle.Alignment := taCenter;
984988 StringGrid1.Canvas.TextStyle := MyTextStyle;
985989 end;
986990 end;
987991```
988992
989993### Multilines in Grids
990994
991995This sample shows how to make multilined text in cell [3,2]. It works the same for DBGrid where OnPrepareCanvas have parameters for dealing with TColumns and from there with TFields.
992996```
993997procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
994998 aState: TGridDrawState);
995999 var
9961000 MyTextStyle: TTextStyle;
9971001 begin
9981002 if (aRow=2) or (aCol=3) then
9991003 begin
10001004 MyTextStyle := StringGrid1.Canvas.TextStyle;
10011005 MyTextStyle.SingleLine := false;
10021006 StringGrid1.Canvas.TextStyle := MyTextStyle;
10031007 end;
10041008 end;
10051009```
10061010
10071011### Validating Entered Values
10081012
10091013Lazarus version 0.9.29 introduces the StringGrid OnValidateEntry event of type TValidateEntryEvent which has the following declaration:
10101014```
10111015type
10121016 TValidateEntryEvent = procedure(sender: TObject; aCol, aRow: Integer; const OldValue: string; var NewValue: string) of object;
10131017```
10141018```
10151019aCol,aRow are the cell coordinates of cell being validated.
10161020 OldValue is the value that was in cells[aCol,aRow] before editing started.
10171021 NewValue is the value that will be finally inserted in cells[aCol,aRow].
10181022```
10191023
10201024Because of the way StringGrid works by setting the cell value while user is editing (see grid's OnSetEditText event and SetEditText method), when the OnValidateEntry event triggers, the cell already contains the entered value (valid or not); using the event arguments OldValue and NewValue the cell value can be validated and changed if needed.
10211025
10221026Usually validation occurs when the user has moved to another cell. If validation then fails, it is desirable to keep the cell editor visible/focused so the entered value can be corrected by user. To let the grid know that validation has failed, an exception needs to be raised. The grid will handle the exception to Application.HandleException and any movement is cancelled.
10231027
10241028For example, suppose that cell[1,1] should hold only values 'A' or 'B', validation could be made with:
10251029```
10261030procedure TForm1.GridValidateEntry(sender: TObject; aCol,
10271031 aRow: Integer; const OldValue: string; var NewValue: String);
10281032 begin
10291033 if (aCol=1) and (aRow=1) then begin
10301034 if grid.Cells[aCol,aRow]<>'A') and grid.Cells[aCol,aRow]<>'B') then begin
10311035 // set a new valid value so user can just press RETURN to continue for example.
10321036 NewValue := 'A';
10331037 // another option is reverting to previous cell value (which is assumed to be valid)
10341038 // NewValue := OldValue;
10351039 // Use EAbort rather than another Exception type to avoid spurious error
10361040 // dialog boxes and backtraces
10371041 raise EAbort.Create('Only A or B are allowed here');
10381042 end else begin
10391043 // if no exception is raised, it is assumed the value is valid, yet if necessary
10401044 // the final value can still be changed by filling NewValue with a different value
10411045
10421046 // computer knows better :)
10431047 if grid.Cells[1,1]='A' then
10441048 NewValue := 'B'
10451049 else
10461050 NewValue := 'A';
10471051 end;
10481052 end;
10491053 end;
10501054```
10511055
10521056### Sorting Columns or Rows
10531057
10541058Property ColumnClickSorts allows grid to be sorted automatically when user clicks a column header. Clicking the same column many times switches the sort order. Default column sort images are shown to indicate which column was clicked.
10551059
10561060In code you can use SortColRow() method.
10571061
10581062The first parameter, IsColumn, is a boolean value which indicates:
10591063
10601064 * True if a column is to be sorted
10611065 * False if a row is to be sorted.
10621066
10631067The second parameter, index, is an integer value:
10641068
10651069 * Index Indicates Column or Row to be sorted.
10661070
10671071The last two parameters are optional, defines subrange of rows or columns to be sorted.
10681072
10691073 * FromIndex (From column or row)
10701074 * ToIndex (To column or row)
10711075
10721076If the last two parameters are not specified, the whole column or row is sorted.
10731077
10741078Sorting uses QuickSort algorithm, it could be changed if a descendant grid overrides the sort() method and calls doCompareCells for cell compare.
10751079
10761080By default it sorts cell content as strings either in ascending or descending order which is selectable with property SortOrder. By default it uses ascending order.
10771081```
10781082// Sort column 3 in ascending order
10791083 grid.SortColRow(true, 3);
10801084
10811085 // Sort column 3 in descending order, skip fixed rows a top
10821086 grid.SortOrder := soDescending; // or soAscending
10831087 grid.SortColRow(true, 3, grid.FixedRows, grid.RowCount-1);
10841088```
10851089
10861090For custom sorting of numbers, dates, states, etc: StringGrid has the OnCompareCells event which users can handle for example this way:
10871091```
10881092procedure TForm1.GridCompareCells(Sender: TObject; ACol, ARow, BCol, BRow: Integer; var Result: integer);
10891093 begin
10901094 // Result will be either <0, =0, or >0 for normal order.
10911095 result := StrToIntDef(Grid.Cells[ACol,ARow],0)-StrToIntDef(Grid.Cells[BCol,BRow],0);
10921096 // For inverse order, just negate the result (eg. based on grid's SortOrder).
10931097 if StringGrid1.SortOrder = soDescending then
10941098 result := -result;
10951099 end;
10961100```
10971101
10981102You can use OnCompareCells also when automatic column sorting is enabled through ColumnClickSorts property.
10991103
11001104### Override automatic sort (ColumnClickSorts set to False). Avoid sort of some columns. Show sort arrows in column header
11011105
11021106(ColumnClickSorts set to False will NOT show sort arrows in column header, so we have to set the arrows.)
11031107```
11041108procedure TForm1.StringGrid1HeaderClick(Sender: TObject; IsColumn: Boolean;
11051109 Index: Integer);
11061110 begin
11071111 if isColumn then with StringGrid1 do
11081112 begin
11091113 col:=Index; // Set column to sort
11101114 if Tag = 0 then Tag :=-1 // Suppose data in column is Ascending
11111115 else Tag := Tag * -1; // Using StringGrid1.Tag to hold sortorder
11121116 // First click of header will sort descending
11131117
11141118 // Activate CompareCells (ativate the sort)
11151119 case Index of
11161120 0: col:=-1; // Do not sort column 0 and do not show sort arrow
11171121 1: SortColRow(True, Index); // The sort will take place in CompareCells as soon as statement is executed
11181122 2: SortColRow(True, Index);
11191123 3: SortColRow(True, Index);
11201124 4: SortColRow(True, Index);
11211125 5: col:=-1; // Do not sort column 5 and do not show sort arrow
11221126 end;
11231127 end;
11241128 end;
11251129
11261130 // Compare returns one of the following values:
11271131 // if A<B return: -1 ( Ascending )
11281132 // if A=B return: 0
11291133 // if A>B return: 1 ( Descending)
11301134
11311135 // How to sort: Text, Integer, Float, DateTime:
11321136
11331137 procedure TForm1.StringGrid1CompareCells(Sender: TObject; ACol, ARow, BCol,
11341138 BRow: Integer; var Result: integer);
11351139
11361140 function CompareTextAsText: Integer; // Text
11371141 begin
11381142 CompareText(Cells[ACol,ARow], Cells[BCol,BRow]);
11391143 end;
11401144
11411145 function CompareTextAsInteger: Integer;
11421146 var i1,i2:Integer;
11431147 begin
11441148 if TryStrToInt(StringGrid1.Cells[ACol,ARow],i1) and // Integer
11451149 TryStrToInt(StringGrid1.Cells[BCol,BRow],i2) then
11461150 Result := CompareValue(i1, i2)
11471151 else Exit(0);
11481152 end;
11491153
11501154 function CompareTextAsFloat: Integer; // Float
11511155 var f1,f2:Extended;
11521156 begin
11531157 if TryStrToFloat(StringGrid1.Cells[ACol,ARow],f1) and
11541158 TryStrToFloat(StringGrid1.Cells[BCol,BRow],f2) then
11551159 Result := CompareValue(f1, f2)
11561160 else Exit(0);
11571161 end;
11581162
11591163 function CompareTextAsDateTime: Integer; // DateTime
11601164 var
11611165 D1, D2 : TDateTime;
11621166 begin
11631167 if Trim(StringGrid1.Cells[ACol,ARow]) <> Trim(StringGrid1.Cells[BCol,BRow]) then
11641168 begin
11651169 if trim(StringGrid1.Cells[ACol,ARow]) = '' then Exit(1);
11661170 if Trim(StringGrid1.Cells[BCol,BRow]) = '' then Exit(-1);
11671171 end
11681172 else Exit(0);
11691173
11701174 if TryStrToDate(StringGrid1.Cells[ACol,ARow], D1, 'yy/mm/dd') and
11711175 TryStrToDate(StringGrid1.Cells[BCol,BRow], D2, 'yy/mm/dd') then
11721176 Result := CompareDateTime(D1, D2)
11731177 else Exit(0);
11741178 end;
11751179
11761180 begin // Don't do anything else here, sort might be done incorrect
11771181 With StringGrid1 do
11781182 begin
11791183 case ACol of
11801184 0 : ; // Do not sort column 0
11811185 1 : Result := CompareTextAsText; // Text
11821186 2 : Result := CompareTextAsInteger; // Integer
11831187 3 : Result := CompareTextAsFloat; // Float
11841188 4 : Result := CompareTextAsDateTime; // DateTime
11851189 end; {Case}
11861190 end;
11871191 if StringGrid1.Tag = -1 then Result := -Result; // Switch the sort order
11881192 end;
11891193
11901194 // DrawCell does the trick, previous arrow images are removed:
11911195 procedure TForm1.StringGrid1DrawCell(Sender: TObject; aCol, aRow: Integer;
11921196 aRect: TRect; aState: TGridDrawState);
11931197
11941198 procedure DrawArrow(ImgIndex:Integer); // Need Up/Down arrows in a ImageList
11951199 begin
11961200 with StringGrid1 do
11971201 TitleImageList.Draw(Canvas, // TitleImageList of StringGrid
11981202 ARect.Right - TitleImageList.Width,
11991203 (ARect.Bottom - ARect.Top - TitleImageList.Height) div 2 + 2, // Position of arrow, may be adjusted.
12001204 ImgIndex );
12011205 end;
12021206 begin
12031207 if Col > 0 then // In this demo no arrows in col 0
12041208 if (ARow = 0) and // Suppose Row 0 as TitleRow
12051209 Assigned(TitleImageList) then // Need Up/Down arrows in a ImageList
12061210 case Columns.Items[aCol].Tag of // Use Tag of Column as index of Ascending/Descending arrow
12071211 -1: DrawArrow(1); // Replace with your Index of down arrow
12081212 0: DrawArrow(-1); // Remove previous image
12091213 1: DrawArrow(2); // Replace with your Index of Up Arrow
12101214 end;
12111215 end;
12121216```
12131217
12141218### Highlighting the selected cell column and row
12151219
12161220In Lazarus revision 40276 an option (**gdRowHighlight**) has been added, it works similar to **goRowSelect** but it uses a lighter color for selection and the focus rect is only selected cell and not in the whole row. This works fine for rows, but how about columns?.
12171221
12181222This section presents a way to highlight columns, rows or both (an example that highlights only column and row headers can be found in lazarus/examples/gridexamples/spreadsheet, this howto is really an extension of that example). This uses two grid events: **OnBeforeSelection** and **OnPrepareCanvas** , drawing is not necessary.
12191223
12201224The event **OnBeforeSelection** is triggered when the selection is about the change, in this event we can know what cell is currently selected and what cell will be selected next. We use this information to invalidate the whole row or column of both the old and the new cells. When the next paint cycle starts, the grid will be instructed to paint the cells that belong to the invalidated areas. One of the first steps for painting is calling the **OnPrepareCanvas** event (if it exists) to setup default canvas properties. We use this event to set up the highlighted row or column:
12211225```
12221226procedure TForm1.gridBeforeSelection(Sender: TObject; aCol, aRow: Integer);
12231227 begin
12241228 // we can decide here if we want highlight columns, rows or both
12251229 // in this example we highlight both
12261230 if Grid.Col<>aCol then
12271231 begin
12281232 // a change on current column is detected
12291233 grid.InvalidateCol(aCol); // invalidate the new selected cell column
12301234 grid.InvalidateCol(grid.Col); // invalidate the current (it will be the 'old') selected column
12311235 end;
12321236 if Grid.Row<>aRow then
12331237 begin
12341238 grid.InvalidateRow(aRow); // invalidate the new selected cell row
12351239 grid.InvalidateRow(grid.Row); // invalidate the current (it will be the 'old') selected row
12361240 end;
12371241 end;
12381242
12391243 procedure TForm1.gridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
12401244 aState: TGridDrawState);
12411245 begin
12421246 if gdFixed in aState then
12431247 begin
12441248 if (aCol=grid.Col) or (aRow=grid.Row) then
12451249 grid.Canvas.Brush.Color := clInactiveCaption; // this would highlight also column or row headers
12461250 end else
12471251 if gdFocused in aState then begin
12481252 // we leave alone the current selected/focused cell
12491253 end else
12501254 if (aCol=Grid.Col) or (aRow=grid.Row) then
12511255 grid.Canvas.Brush.Color := clSkyBlue; // highlight rows and columns with clSkyBlue color
12521256 end;
12531257```
12541258
12551259### Avoiding the empty space at the right of the last column
12561260
12571261The grid must be in column mode to achieve this. When the grid property `AutoFillColumns` is `true` the empty space is divided by all columns which have a non-zero `SizePriority`. Therefore, you should follow these steps to fill up all horizontal space of a grid:
12581262
12591263 * Set `AutoFillColumns` to `true`.
12601264 * Using the Columns editor, add as many columns as you need.
12611265 * Select each column in the object tree above the object inspector and set its `SizePriority` to `0`.
12621266 * Now decide while column is widened such that all empty space is used up. Set the `SizePriority` of this column to `1`.
12631267
12641268### How to scroll a Grid in "realtime" (moving the scrollbar via mouse)
12651269
12661270If you scroll a Grid by moving the scrollbar with the mouse then the Grid per default is not moved "in realtime". Only after you release the mouse, the Grid "jumps" to its new position. You can enable a scrolling in "realtime" by adding Option `goThumbTracking`.
12671271
12681272Example:
12691273```
12701274StringGrid1.Options:=StringGrid1.Options + [goThumbTracking];
12711275```
12721276
12731277Unfortunately this Option is neither documented in `<lazarus-installdir>\lcl\grids.pas` (ver 2.0.6) nor in <https://lazarus-ccr.sourceforge.io/docs/lcl/grids/tgridoptions.html>
12741278
12751279### How to resize a row based on cells content
12761280
12771281Lacak in bug report [#38473](<https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/38473>) contributed the following code:
12781282```
12791283type
12801284 { TStringGridHelper }
12811285 TStringGridHelper = class helper for TStringGrid
12821286 protected
12831287 procedure AutoSizeRow(aRow: integer);
12841288 end;
12851289
12861290 { TStringGridHelper }
12871291 procedure TStringGridHelper.AutoSizeRow(aRow: integer);
12881292 var
12891293 aCanvas: TCanvas;
12901294 aCol, maxRowHeight:integer;
12911295 aText: String;
12921296 textRect: TRect;
12931297 begin
12941298 aCanvas := GetWorkingCanvas(Canvas);
12951299 maxRowHeight := DefaultRowHeight;
12961300 for aCol:=0 to ColCount-1 do begin
12971301 aText := Cells[aCol,aRow];
12981302 textRect := Rect(0, 0, ColWidths[aCol], MaxInt);
12991303 DrawText(aCanvas.Handle, PChar(aText), Length(aText), textRect, DT_CALCRECT or DT_WORDBREAK);
13001304 if maxRowHeight < textRect.Height then
13011305 maxRowHeight := textRect.Height
13021306 end;
13031307 if aCanvas<>Canvas then FreeWorkingCanvas(aCanvas);
13041308 RowHeights[aRow] := maxRowHeight+2;
13051309 end;
13061310```
13071311
13081312Add that somewhere in your source code and then you can resize a row as if the AutoSizeRow procedure is part of the stringgrid, example:
13091313```
13101314// Resize the row index 2
13111315 grid.AutoSizeRow(2);
13121316```
13131317
13141318## DBGrids Howto and Examples
13151319
13161320### Example: How to set a memo editor for dbgrids
13171321
13181322You can, of course, use another control instead of a TMemo. Adjust to taste. Adapted from [[1]](<http://forum.lazarus.freepascal.org/index.php?topic=3640.0>) (simplified to use the SelectedFieldRect property, see [[2]](<http://forum.lazarus.freepascal.org/index.php/topic,8713.msg43288.html#msg43288>))
13191323
13201324 * Place a memo control (or whatever control you want) on your form, set whatever properties you want and set visible to false. This will be used when editing a grid cell. We'll use GridCellMemo in this example.
13211325 * In the OnSelectEditor event put the following code - adapt if you don't want to edit logical column 3, physical column 4:
13221326
13231327```
13241328if (Column.DesignIndex = 3) then
13251329 begin
13261330 GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
13271331 GridCellMemo.Text:=Column.Field.AsString;
13281332 Editor := GridCellMemo;
13291333 end;
13301334```
13311335
13321336 * Suppose your datasource is called Datasource1 and DBGrid is called ResultsGrid. Then, in the OnEditingDone event for the GridCellMemo put the following:
13331337
13341338(The original forum post used the OnChange event, which would fire each time the content is changed. Using OnEditingDone only fires after the user is finished with his edits.)
13351339```
13361340if not(Datasource1.State in [dsEdit, dsInsert]) then
13371341 Datasource1.Edit;
13381342
13391343 Datasource1.DataSet.FieldByName(ResultsGrid.SelectedField.FieldName).AsString:=GridCellMemo.Text;
13401344```
13411345
13421346**Beware** Using a normal control as a custom editor has many drawbacks: it won't resize or reposition itself when resizing the columns or scrolling through the grid, you have to take care yourself of modifying the data in the grid (as explained above), it doesn't work properly with DbGrids and interferes with normal grid navigation using the keyboard. The grid interacts with the editor using some special grid messages, so it's better to subclass a control in order to manage those messages, as explained in chapter five of [this document](<http://www.freepascal.org/~michael/articles/grids/grids.pdf>).
13431347
13441348**Beware** If you have more than one grid in the same form use a different custom editor for each one: if you use the same control bad things will happen since both grids will try to use it at once.
13451349
13461350### Example: Avoid displaying text fields as "(Memo)" for DBGrids
13471351
13481352now (08/2023) exist the dbgrid option dgDisplayMemoText in object inspector - option
13491353
13501354
13511355When you use SQL statements like "Select * from A" you might see "(Memo)" instead of your content in a grid cell. There are many ways to fix this, but maybe the easiest is to change your statement "Select * from A" to "Select Cast(Column as TEXT) as Column from A". This is a common problem when you use a DBGrid component.
13521356```
13531357var
13541358 sql : UTF8String;
13551359 Queryu : TSQLQuery;
13561360 .....
13571361 sql := 'SELECT cast(Name as TEXT) as Name FROM Client';
13581362 Queryu.SQL.Text:=sql;
13591363```
13601364
13611365
13621366
13631367for SQLITE3, to avoid cast function usage, just give len for fields in your sqlite database :
13641368
13651369\- when you import from csv or create table : create table mytable (myfield TEXT (10))
13661370
13671371\- or using sqlstudio or other to give len to fields
13681372
13691373### Sorting columns or rows in DBGrid with sort arrows in column header
13701374
13711375Here is an example that will sort a **DBgrid** using the **OnTitleClick** event and a **TSQLQuery** and indexes. This should also work for any compatible data set such as **TbufDataset**. The function uses the **column.tag** property to store the sort state for each column you click on, so when you go back to one you have already sorted it will pick the correct sort arrow to display.
13721376
13731377**Prerequisites:**
13741378
13751379 * You will need an imagelist to store the up/down sort arrows. Assign your imagelist to the dbgrid's **TitleImageList** property.
13761380 * Ensure the **[TSQLQuery.MaxIndexesCount](<http://lazarus-ccr.sourceforge.net/docs/fcl/sqldb/tsqlquery.maxindexescount.html> "doc:fcl/sqldb/tsqlquery.maxindexescount.html")** is large enough to hold the new indexes you will be creating. For this example I had it set to 100.
13771381 * You will also need a private var to store the last column used to sort, for this example I have called it **FLastColumn**.
13781382
13791383For this example the **TSQLQuery** used is called OpenQuery.
13801384
13811385![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** As of March 21st 2013, **TSQLQuery** has no way to clear indexes, but as a work around you can set **unidirectional** to true, then set it to false and this will clear the indexes.
13821386
13831387In order to reuse the **TSQLQuery** component with another SQL statement, the indexes must be cleared after sorting or an exception will be raised when you open the **TSQLQuery** with a different SQL statement.
13841388```
13851389FLastColumn: TColumn; //store last grid column we sorted on
13861390
13871391 procedure TSQLForm.ResultsGridTitleClick(Column: TColumn);
13881392 const
13891393 ImageArrowUp=0; //should match image in imagelist
13901394 ImageArrowDown=1; //should match image in imagelist
13911395 var
13921396 ASC_IndexName, DESC_IndexName:string;
13931397 procedure UpdateIndexes;
13941398 begin
13951399 // Ensure index defs are up to date
13961400 OpenQuery.IndexDefs.Updated:=false; {<<<--This line is critical. IndexDefs.Update will not
13971401 update if already true, which will happen on the first column sorted.}
13981402 Openquery.IndexDefs.Update;
13991403 end;
14001404 begin
14011405 ASC_IndexName:='ASC_'+Column.FieldName;
14021406 DESC_IndexName:='DESC_'+Column.FieldName;
14031407 // indexes can't sort binary types such as ftMemo, ftBLOB
14041408 if (Column.Field.DataType in [ftBLOB,ftMemo,ftWideMemo]) then
14051409 exit;
14061410 // check if an ascending index already exists for this column.
14071411 // if not, create one
14081412 if OpenQuery.IndexDefs.IndexOf(ASC_IndexName) = -1 then
14091413 begin
14101414 OpenQuery.AddIndex(ASC_IndexName,column.FieldName,[]);
14111415 UpdateIndexes; //ensure index defs are up to date
14121416 end;
14131417 // Check if a descending index already exists for this column
14141418 // if not, create one
14151419 if OpenQuery.IndexDefs.IndexOf(DESC_IndexName) = -1 then
14161420 begin
14171421 OpenQuery.AddIndex(DESC_IndexName,column.FieldName,[ixDescending]);
14181422 UpdateIndexes; //ensure index defs are up to date
14191423 end;
14201424
14211425 // Use the column tag to toggle ASC/DESC
14221426 column.tag := not column.tag;
14231427 if boolean(column.tag) then
14241428 begin
14251429 Column.Title.ImageIndex:=ImageArrowUp;
14261430 Openquery.IndexName:=ASC_IndexName;
14271431 end
14281432 else
14291433 begin
14301434 Column.Title.ImageIndex:=ImageArrowDown;
14311435 OpenQuery.IndexName:=DESC_IndexName;
14321436 end;
14331437 // Remove the sort arrow from the previous column we sorted
14341438 if (FLastColumn <> nil) and (FlastColumn <> Column) then
14351439 FLastColumn.Title.ImageIndex:=-1;
14361440 FLastColumn:=column;
14371441 end;
14381442```
14391443
14401444### Selecting Records in a DBGrid using checkboxes
14411445
14421446The objective is to be able to select arbitrary records in a dbgrid using checkboxes, the grid has the ability to show checkboxes automatically when it detects there are boolean fields, for other field types the user can manually choose the cbsCheckboxColumn ButtonStyle for the column. For this kind of columns the user just click the checkbox and the field content is modified accordingly.
14431447
14441448But what happen if there is no such available field in our dataset? or we don't want the grid enter edit state when checking the checkbox?. Adding a fieldless column with ButtonStyle=cbsCheckboxColumn will show all checkboxes grayed and disabled because there is no field linked to this column and so nothing to modify. As we want to handle the checkbox state ourselves we need to store the state somewhere for each record. For this we can use the class TBookmarklist (defined in dbgrids.pas unit) where property CurrentRowSelected can tell if the current record is selected or not. By using dbgrid events OnCellClick and OnUserCheckboxState we can track the checkbox state.
14451449
14461450Note this technique needs Lazarus r31148 or later which implements event OnUserCheckboxState.
14471451```
14481452...
14491453 uses ..., dbgrids, stdctrls, ...
14501454
14511455 type
14521456
14531457 { TForm1 }
14541458
14551459 TForm1 = class(TForm)
14561460 ...
14571461 procedure DBGrid1CellClick(Column: TColumn);
14581462 procedure DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
14591463 procedure FormCreate(Sender: TObject);
14601464 procedure FormDestroy(Sender: TObject);
14611465 ...
14621466 private
14631467 RecList: TBookmarklist;
14641468 ...
14651469 end;
14661470
14671471 procedure TForm1.DBGrid1CellClick(Column: TColumn);
14681472 begin
14691473 if Column.Index=1 then
14701474 RecList.CurrentRowSelected := not RecList.CurrentRowSelected;
14711475 end;
14721476
14731477 procedure TForm1.FormCreate(Sender: TObject);
14741478 begin
14751479 RecList := TBookmarkList.Create(DbGrid1);
14761480 end;
14771481
14781482 procedure TForm1.FormDestroy(Sender: TObject);
14791483 begin
14801484 RecList.Free;
14811485 end;
14821486
14831487 procedure TForm1.DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
14841488 begin
14851489 if RecList.CurrentRowSelected then
14861490 AState := cbChecked
14871491 else
14881492 AState := cbUnchecked;
14891493 end;
14901494```
14911495
14921496### Example: How to export a DBGrid to a text file
14931497
14941498There are several ways to create a text file from the content of a DBGrid, normally people will tell you that instead of thinking of a DBGrid as a data container one should look a the linked dataset instead, but what happen when the text representation you want must match the grids appearance of the columns visibility, width or order? one solution is to use the LazReport suite that has a component specifically for producing a report from a DbGrid, this report can then be exported to several other formats including a text format. Here another simple solution is presented which can also be used as the basis for other text format exporters, for example, a simple html exporter.
14951499```
14961500// aGrid: The grid to be exported
14971501 // lpp: Lines per page. Not including the text header
14981502 // pageBreak: insert a page break that some printers could use for starting a new page
14991503 // require: uses LazUTF8
15001504 function GridTextExporter(aGrid: TDbGrid; lpp: Integer; pageBreak:boolean): TStringList;
15011505 const
15021506 PX_PER_CHAR = 7;
15031507
15041508 var
15051509 Ds: TDataset;
15061510 book: TBookMark;
15071511 recCount: Integer;
15081512 line: string;
15091513 column: TColumn;
15101514
15111515 function WidthToChar(aWidth: Integer): integer;
15121516 begin
15131517 result := trunc(aWidth/PX_PER_CHAR);
15141518 end;
15151519
15161520 procedure AddNext(theText: string; alignment: TAlignment);
15171521 var
15181522 width: Integer;
15191523 begin
15201524 if (line<>'') and (line<>#12) then
15211525 line := line + ' ';
15221526
15231527 width := WidthToChar(Column.Width);
15241528
15251529 case Alignment of
15261530 taRightJustify: line := line + UTF8PadLeft(theText, width);
15271531 taCenter: line := line + UTF8PadCenter(theText, width);
15281532 else line := line + UTF8PadRight(theText, width);
15291533 end;
15301534 end;
15311535
15321536 procedure CollectHeader;
15331537 begin
15341538 AddNext(Column.Title.Caption, Column.Title.Alignment);
15351539 end;
15361540
15371541 procedure CollectField;
15381542 var
15391543 field: TField;
15401544 begin
15411545 field := Column.Field;
15421546
15431547 if (field.DataType=ftMemo) and (dgDisplayMemoText in aGrid.Options) then
15441548 AddNext(field.AsString, Column.Alignment)
15451549 else if Field.DataType<>ftBlob then
15461550 AddNext(field.DisplayText, Column.Alignment)
15471551 else
15481552 AddNext('(blob)', Column.Alignment);
15491553 end;
15501554
15511555 procedure LoopColumns(printingRecord: boolean);
15521556 var
15531557 c: TCollectionItem;
15541558 begin
15551559 if (not printingRecord) and pageBreak and (result.count>0) then
15561560 line := #12
15571561 else
15581562 line := '';
15591563 for c in aGrid.Columns do begin
15601564 Column := TColumn(c);
15611565 if Column.Visible and (Column.Width>=PX_PER_CHAR) then begin
15621566 if printingRecord then CollectField
15631567 else CollectHeader;
15641568 end;
15651569 end;
15661570 result.add(line);
15671571 end;
15681572
15691573 begin
15701574 result := TStringList.create;
15711575 Ds := aGrid.DataSource.DataSet;
15721576 Ds.DisableControls;
15731577 book := Ds.GetBookmark;
15741578 try
15751579 Ds.First;
15761580 recCount := 0;
15771581 while not DS.EOF do begin
15781582 if RecCount mod lpp = 0 then
15791583 LoopColumns(false);
15801584 LoopColumns(true);
15811585 inc(recCount);
15821586 Ds.Next;
15831587 end;
15841588 finally
15851589 Ds.GotoBookmark(book);
15861590 Ds.FreeBookmark(book);
15871591 Ds.EnableControls;
15881592 end;
15891593 end;
15901594```
15911595
15921596To use it one simply does:
15931597```
15941598var
15951599 L: TStringList;
15961600 begin
15971601 L := GridTextExporter(grid, 80, true);
15981602 L.SaveToFile('grid.txt');
15991603 L.Free;
16001604 end;
16011605```
16021606
16031607---
1604-*From the Free Pascal wiki, page "Grids Reference Page", as the Internet Archive kept it on 2024-12-09, last changed on 2024-07-10. 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).*
1608+*From the Free Pascal wiki, page "Grids Reference Page", as the Internet Archive kept it on 2025-04-23, last changed on 2025-04-15. 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