Sign in Join
Wiki copytemporary

Grids Reference Page/es: what changed

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

OldNewLine
11## Objetivo
22
33Este texto trata de enseñar al usuario algunos aspectos de los componentes tipo grid ("rejilla" por traducirlo de alguna forma). También tiene el propósito de servir de guía para usuarios que nunca hayan utilizado grids con anterioridad (los usuarios experimentados generalmente solo necesitan las referencias sobre nuevas funcionalidades). Por lo tanto este documento tratará de conseguir los siguientes objetivos:
44
551. Servir de introducción a los componentes tipo grid para la gente que tenga pocos o ningún conocimiento previo de Lazarus.
662. Documentar las diferencias con respecto a los componentes tipo grid utilizados en Delphi.
773. Documentar la nueva funcionalidad de las grids en Lazarus.
884. Crear referencias y ejemplos para estos componentes.
99
1010## Descripción
1111
1212Una grid es un componente que aporta un significado para los datos mostrados en un formato tabulado (tabla). La característica más obvia de las grids es que están compuestas por celdas (cells) formando filas (rows) y columnas (cols).
1313
1414El tipo de información que pueden mostrar es muy amplio y mayoritariamente depende de lo que el usuario necesita mostrar. Generalmente esta información consiste en texto, colores, imágenes o una combinación de estos tres.
1515
1616Dada la gran variedad de información que pueden representar, se ha creado una serie de grids más específicas dependiendo del tipo de información a mostrar. Por ejemplo existe un tipo de grid para mostrar texto, **StringGrid**, de la cual podemos encontrar documentación [aquí](http://lazarus-ccr.sourceforge.net/docs/lcl/grids/tstringgrid.html)
1717
1818El siguiente diagrama muestra el árbol de herencia con los tipos existentes:
1919
2020## Árbol de herencia
2121
22-<figure>
2322<img src="diagrama_grids.png" title="diagrama_grids.png" width="750" />
24-<figcaption>diagrama_grids.png</figcaption>
25-</figure>
2623
2724## Un ejemplo inicial
2825
2926Como uno de los objetivos de ésta página es ayudar a la gente con poco o ningún conocimiento previo de Lazarus, nos vamos a permitir poner un breve ejemplo como punto de partida para entender las grids en acción. Porque no, hagamos un tradicional "¡ Hola Mundo !" como ejemplo del uso del componente TStringGrid.
3027
31281. Crear una nueva aplicación.
3229 - Desde el menú principal seleccionar: Archivo -\> Nuevo ... -\> Proyecto -\> Aplicación.
3330 - Una vez selecciona Aplicación pulsamos Aceptar.
3431 - Nos muestra un formulario (Form1) vacío nuevo.
35322. Situar una grid (rejilla) sobre el formulario Form1.
3633 - Desde la paleta de componentes seleccionar la solapa "Additional".
3734 - Hacer Click sobre el icono TStringGrid ![](/wiki-images/6/67/Tstringgrid.png).
3835 - Hacer Click sobre el formulario, cerca de la esquina superior izquierda. De esta forma aparece una grid nueva vacía.
39363. Situar un pulsador (button) en el formulario.
4037 - Desde la paleta de componentes seleccionar la solapa "Standard".
4138 - Hacer Click sobre el icono TButton ![](/wiki-images/b/b2/Tbutton.png).
4239 - Hacer Click sobre un área vacía del formulario. De esta forma aparece un nuevo pulsador(button).
43404. Hacer doble click sobre el pulsador del paso 3 y donde sitúa el cursor parpadeante (código para el manejador (handler) del pulsador) escribir el siguiente fragmento de código.
4441 - ``` pascal
4542 StringGrid1.Cells[1,1] := '¡ Hola Mundo !';
4643 ```
4744
4845 - Ejecuta el programa haciendo Click en el icono de play ![](/wiki-images/8/85/Menu_run.png).
49465. Una vez que compila correctamente el programa aparece el formulario con un pulsador, pues bien, si hacemos Click sobre el pulsador "Button1" deberíamos visualizar ¡ Hola Mundo ! en la celda que hemos especificado en este caso la correspondiente a la fila 1 y columna 1.
5047
5148## Diferencias entre las grids de Delphi y las de Lazarus
5249
5350Los componentes tipo grid actuales de Lazarus presentan varias diferencias con respecto a las disponibles en Delphi. Esto se debe principalmente a que las grids de Lazarus fueron creadas desde cero y no se buscaba que tuviesen una total compatibilidad con las de Delphi,
5451
5552Posteriormente, se puso como objetivo tener dicha compatibilidad y las grids se empezaron a diseñar lo más parecidas al interface que tienen en Delphi, pero incluso cuando esto se empezó a implementar así tampoco se realizó un excesivo esfuerzo para hacer coincidir cada propiedad equivalente en Delphi.
5653
5754También, debido a que el desarrollo interno de las grids es muy diferente, algunos aspectos no son posibles o necesitan realizarse de un modo diferente en Lazarus. Además (debido a que las grid de Lazarus internamente son muy diferentes a las de Delphi)algunas funcionalidades de Delphi no son posibles o necesitan ser emuladas de una manera diferente en las grids de Lazarus. De todas formas se ha alcanzado un alto grado de compatibilidad, lo cual es deseable.
5855
5956### Diferencias
6057
6158Las siguientes son diferencias evidentes:
6259
6360- Editores de celdas (Cell).
6461- Comportamiento en tiempo de diseño.
6562- El dibujo de las celdas tiene algunas diferencias, ver la sección de personalización de grids para más detalles.
6663
6764### Nuevas funcionalidades
6865
6966- Columnas personalizadas.
7067- Eventos.
7168- Editor de Grid.
7269
7370### Ajustes para afianzar la compatibilidad con grids de Delphi
7471
7572Aquí hay una lista de propiedades y ajustes que se pueden realizar para lograr que las grids de Lazarus se parezcan o comporten de modo similar la las de Delphi. Estos ajustes están basados en una nueva grid creada. Las entradas etiquetadas con \[code\] necesitan establecerse con código, mientras que las etiquetadas como \[Design\] pueden modificarse en tiempo de diseño.
7673
7774- \[Design\] TitleStyle := tsStandard;
7875- \[Design\] DefaultRowHeight := 24;
7976- \[Code\] EditorBorderStyle := bsNone; // esto debería funcionar únicamente en windows.
8077- \[Code\] UseXORFeatures := true;
8178- \[Code\] AllowOutBoundEvents := False; {r10992 o posterior}
8279- \[Code\] FastEditing := False; (soportado en dbgrid. StringGrid req. r10992 o posterior)
8380- \[Design\] AutoAdvance := aaNone;
8481
8582## Referencia de Grids
8683
8784### Información
8885
8986El punto de partida para referencias sobre TCustomGrid, TDrawGrid, TCustomDrawGrid, TCustomStringGrid y TStringGrid es: [Referencia unit Grids.pas](http://lazarus-ccr.sourceforge.net/docs/lcl/grids/index.html)
9087
9188Para TCustomDBGrid y TDBgrid es: [Referencia: unit DBGrids.pas](http://lazarus-ccr.sourceforge.net/docs/lcl/dbgrids/index.html)
9289
9390Hasta hace poco no había demasiado contenido en estos enlaces, debido en parte a la carencia de utilidades que permiten mantener facilmente su contenido: pero finalmente estas herramientas han sido creadas y se ha llenado el vacio de contenido que tenían.
9491
9592En general, cualquier referencia Delphi acerca de las grids deberían ayudar en el uso de las equivalentes de Lazarus (pero no no se debe olvidar que existen algunas diferencias tal como se ha indicado); con esto en mente y como un lugar temporal de referencia de información, este espacio se utilizará para documentar cosas que no funcionan de igual manera en Delphi, aparte de las nuevas funcionalidades de las existentes en Lazarus.
9693
9794Cosas por hacer: el resto de esta sección desaparecerá. Su contenido se trasladará a [Referencia: unit Grids.pas](http://lazarus-ccr.sourceforge.net/docs/lcl/grids/index.html)
9895
9996### TCustomGrid
10097
10198Ver la [[doc:lcl/grids/tcustomgrid.html|Referencia: TCustomGrid]]
10299
103100#### Propiedad AllowOutboundEvents
104101
105102Protegida en TCustomGrid, pública en TCustomDrawGrid y descendientes. Normalmente cuando un usuario hace click en un punto sobre el espacio vacío después de las celdas (por ejemplo si la grid tiene tres filas pero el usuario hace click sobre una cuarta línea imaginaria), el foco actual se mueve a la celda más cercana a este punto. Llamamos a esto un evento outbound. El valor por defecto de esta propiedad está establecido a True tal como se estableció desde el principio del comportamiento de las grid. Esta propiedad se ha añadido para simular el comportamiento de Delphi donde los eventos outbound no están disponibles, por lo que para habilitar su compatibilidad con Delphi se debe establecer esta propiedad a falso.
106103
107104#### Propiedad Columns
108105
109106Lazarus incluye la propiedad *columns*' en las grid tipo TStringGrid y TDrawGrid. Esta propiedad añade lo que llamamos columnas personalizadas. Las columnas personalizadas son una colección de objetos con propiedades que se aplican al conjunto completo de columnas que contiene la grid, por ejemplo títulos de las columnas (para una StringGrid sobreescribirá el valor especificado en las propiedades \[ColumnIndex, RowTitle\] correspondientes de las celdas, alineación de texto, color de fondo, etitor preferido, etc.
110107
111108Las columnas personalizadas añaden propiedades extra o reemplazan los valores por defecto de las propiedades en las columnas de la grid normal. No solamente esto, el valor **grid.ColCount** puede incrementarse o decrementarse para contabilizar el numero de columnas personalizadas añadidas a la grid. En este punto, esto significa **grid.ColCount** = **grid.FixedCols** + **grid.Columns.Count**.
112109
113110Por ejemplo, si a una grid base con with ColCount := 5 y FixedCols := 2 hacemos:
114111
115112- Añadir 3 columnas personalizadas, la grid base estará exactamente como antes, 2 columnas fijas y 3 columnas normales.
116113- Añadir una columna personalizada, la grid base tendrá ColCount := 3, esto es 2 columnas fijas y una columna normal.
117114- Añadir 4 columnas personalizadas, la grid base tendrá ColCount := 6, esto es 2 columnas fijas y 4 columnas normales.
118115
119116Por tanto podemos concluir que:
120117
121118- Las propiedades de columnas fijas o su cuenta no son afianzadas o modificadas respectivamente por las columnas personalizadas.
122119- **grid.ColCount** es usualmente diferente de **grid.Columns.Count** (**grid.ColCount**=**grid.Columns.Count** solamente cuando **FixedCols**=0).
123120
124121En tiempo de diseño el usuario puede acceder a las propiedades de **columns** mediante el Inspector de Objetos para arrancar el editor de columnas. Desde él, se pueden añadir, eliminar o modificar las columnas personalizadas. El editor muestra una lista de las columnas personalizadas mediante la selección de elementos en el listado que nos ofrece el inspector de objetos con las propiedades rellenas para cada columna. El listado de las columnas personalizadas se encuentra también disponible en la vista en árbol de componentes del Inspector de Objetos, donde se pueden añadir, borrar o modificar columnas. Aparecen a un menor nivel bajo la grid (regilla) contenedora.
125122
126123En tiempo de ejecución, las columnas se pueden modificar con un código como este:
127124
128125``` pascal
129126 var
130127 c: TGridColumn;
131128 begin
132129 // Añade una columna personalizada a la grid
133130 c := Grid.Columns.Add;
134131 // La modifica
135132 c.title.caption := 'Precio'; // Establece el valor caption de la columna.
136133 c.align := taRightJustify; // Alinea a la derecha el contenido de la columna.
137134 c.color := clMoneyGreen; // Cambia el valor por defecto a clMoneyGreen.
138135 c.Index := 0; // La hace la primera columna.
139136 // Accede a una columna existente
140137 grid.columns[0].Width := 60; // Cambia el ancho de la columna 0 a 60 pixeles.
141138 // Elimina una columna existente.
142139 grid.columns.delete(0); // Elimina la columna 0
143140 ....
144141 end;
145142```
146143
147144Adicionalmente, cuando se utilizan columnas personalizadas, las grids no permiten la modificación directa de **grids.colcount**; añadir o eliminar columnas se debería hacer utilizando la propiedad **columns**. La explicación es que hay una inconsistencia al remover gradualmente las columnas personalizadas mediante el uso de **ColCount**, cuando **ColCount** llega a **FixedCols**, la grid no tiene más columnas personalizadas. Si en ese momento incrementamos **ColCount**, la nueva columna no será del tipo personalizado sino una columna normal.
148145
149146Actualmente no hay planes para hacer que las grids utilicen únicamente columnas personalizadas.
150147
151148### TCustomDBGrid
152149
153150TCustomDBGrid es la base para TDBGrid.
154151
155152No exponen las propiedades **Col** y **Row**. Para ir a una determinada columna, utilizar e.g. la propiedad **SelectedIndex**.
156153
157154Un método público interesante es **AutoSizeColumns**.
158155
159156#### Procedimiento AutoSizeColumns
160157
161158Este procedimiento establece el ancho (**width**) al tamaño que abarque el ancho mayor del texto que encuentre. Esto se puede utilizar después de cargar un dataset estableciéndolo a Activo. Sin embargo, contrariamente a TCustomStringGrid.AutoSizecolumns (ver más abajo), esto establecerá tus columnas muy anchas a menos que tengas habilitada la propiedad dgAutoSizeColumns.
162159
163160#### Procedimiento InplaceEditor
164161
165162Ver ejemplo del bug 23103 - *e inserta explicación de lo que hace y porque se necesita. ¿Validar valores de entrada? ¿Cambiar lo que se muestra?*
166163
167164``` pascal
168165procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: char);
169166var
170167 S: String;
171168begin
172169 if (Key in [',','.']) then
173170 begin
174171 //Al contrario que Delphi no todos los InPlaceEditors son editores para el tipo string, por tanto verificalo.
175172 if (DBGrid1.InplaceEditor is TStringCellEditor) then
176173 begin
177174 S := TStringCellEditor(DBGrid1.InplaceEditor).EditText;
178175 if Pos(',',S) > 0 then
179176 Key := #0
180177 else
181178 Key := ',';
182179 end;
183180 end;
184181end;
185182```
186183
187184### TCustomStringGrid
188185
189186TCustomStringGrid sirve como base para TStringGrid. Se puede utilizar para componentes derivados TStringGrid que necesiten ocultar propiedades publicadas. Ver [[new intermediate grids]] para más información.
190187
191188Las siguientes propiedades o métodos son públicos y están además disponibles para TStringGrid.
192189
193190Ver la referencia completa para [[doc:lcl/grids/tcustomstringgrid.html|TCustomStringGrid]]
194191
195192#### Procedimiento AutoSizeColumn(aCol: Integer);
196193
197194Este procedimiento establece el ancho de columna de forma sea suficiente para contener el tamaño del texto más largo que encuentre en todas las filas de la columna aCol. Consejo: ver la opción goDblClickAutoSize para permitir a las columnas redimensionarse automáticamente cuando se realiza el dobleclick en el borde de la columna.
198195
199196#### Procedimiento AutoSizeColumns;
200197
201198Redimensiona automáticamente ***todas*** las columnas ajustándolas para que quepa el texto más ancho en cada columna. Este es un método rápido de aplicar AutoSizeColumn() para todas las columnas de la grid.
202199
203200#### Procedimiento Clean; overload;
204201
205202Limpia todas las celdas de la grid, fijas o no fijas.
206203
207204#### Procedimiento Clean(CleanOptions: TGridZoneSet); overload;
208205
209206Limpia todas las celdas en la grid sujetas a lo establecido en CleanOptions. Ver [[doc:lcl/grids/tgridzoneset.html|TGridZoneSet]] para mas información. Algunos ejemplos:
210207
211208- Limpia todas las celdas: grid.Clean(\[\]); (lo mismo que grid.clean)
212209- Limpia todas las celdas no fijas: grid.Clean(\[gzNormal\]);
213210- Limpia todas las celdas salvo no toca las cabeceras de las columnas: Grid.Clean(\[gzNormal, gzFixedRows\]);
214211
215212#### Procedimiento Clean(StartCol,StartRow,EndCol,EndRow: integer; CleanOptions:TGridZoneSet); overload;
216213
217214Hace lo mismo que Clean(CleanOptions:TGridZoneSet) pero restringiéndolo a lo siguiente StartCol,StartRow,EndCol y EndRow, por tanto acota el contenido a limpiar estableciendo tanto columna como fila de inicio y columna y fila final inclusive. Ejemplos:
218215
219216- Limpia las columnas de indice 4 hasta 6 pero no toca las cabeceras de columna de la grid: algunas variaciones, Grid.Clean(4,Grid.FixedRows,6,Grid.RowCount-1,\[\]); Grid.Clean(4,0,6,Grid,RowCount-1, \[gzNormal\]); etc.
220217
221218#### Procedimiento Clean(aRect: TRect; CleanOptions: TGridZoneSet); overload;
222219
223220Lo mismo que Clean(StartCol,StartRow,EndCol,EndRow, CleanOptions), pero tomando un rectánguno TRect seleccionado en lugar de coordenadas de celdas indivisutales. Útil para limpiar la selección: grid.Clean(Grid.Selection,\[\]);
224221
225222==== Procedimiento SaveToCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true); ====
226223
227224Salva el contenido de la grid a un fichero con formato CSV (Comma Separated Values) conteniendo valores separados por comas (añadido en Lazarus r32179).
228225
229226El argumento AFilename especifica el nombre de fichero en el que se salvará el contenido. Si el fichero existe, se sobreecribe su contenido. Caso de no existir previamente el fichero se crea.
230227
231228Se utiliza ADelimiter (como argumento opcional) para que utilice un separador personalizado en lugar de comas. Por defecto se genera un formato CSV (esto es, ADelimiter:=',';), para poner como separador un tabulador ADelimiter debería valer \#9, correspondiente a TAB.
232229
233230Para decidir si se incluye una "fila de cabecera" o no, se utiliza el parámetro WithHeader. La fila de cabecera es un listado de nombres de campos al comienzo del fichero de salida; su contenido procede de la última fila fija de la grid.
234231
235232Hay una excepción a esta regla: si la grid tiene columnas personalizadas entonces el contenido de la fila de cabecera procede de los títulos de las columnas personalizadas y no del contenido de las celdas de la fila fija.
236233
237234Si WithHeader vale true y la grid no incluye una fila fija o columnas personalizadas, entonces se toma el contenido del la ficha de cabecera desde la primera fila que se encuentra en la grid.
238235
239236Los datos normales de salida CSV deben comenzar en la primera fila *no fija* de la grid.
240237
241238==== Procedimiento LoadFromCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true); ====
242239
243240Carga el contenido de la grid desde un fichero con formato de valores separados por coma (CSV) (añadido en Lazarus r32179).
244241
245242Las columnas serán añadidas o borradas a o desde la grid según se necesite de acuerdo al número de campos incluidos en cada línea del fichero CSV. Realizar la carga desde un fichero CSV no modificará el número de filas fijas que ya existan en la grid.
246243
247244El argumento*Afilename* especifica el nombre de fichero con el contenido CSV.
248245
249246*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 ;
250247
251248El parámetro *WithHeader* se utiliza para decidir si la primera línea en el fichero CSV se considera como la "cabecera de fila" o no. Si la grid tiene filas fijas y *WithHeader* vale true, entonces los captions de la columna de la última fila fija se utomarán de la fila de cabecera. Ten en cuenta de todas forma que si la grid tiene columnas personalizadas, entonces la fila de cabecera se utilizará como fuente para los títulos columna y los títulos de las columnas personalizadas se muestran siempre en la primera fila fija u oculta si no existen filas fijas en la grid.
252249
253250Si el procedimiento *LoadFromCSVFile* presenta dificultades para cargar el fichero CSV (e.g. comillas o espacios interpretados incorrectamente), entonces puedes realizar la carga manualmente utilizando e.g. [[CsvDocument]]... y por supuesto un parche para *LoadFromCSVFile* es siempre bienvenido.
254251
255252#### Propiedad Cols\[index: Integer\]: TStrings lee GetCols escribe SetCols;
256253
257254Obtiene/establece un listado de cadenas desde/hacia la columna índice de la grid comenzando desde la fila índice 0 hasta RowCount-1.
258255
259256##### Ejemplos
260257
261258- Ejemplo para establecer: Establece el contenido de la tercera columna en la grid desde una ListBox
262259
263260``` pascal
264261Grid.Cols[2] := ListBox1.Items;
265262```
266263
267264- Ejemplo para obtener: Establece el contenido de un Listbox desde la columna índice 4 de la grid.
268265
269266``` pascal
270267procedure TForm1.FillListBox1;
271268var
272269 StrTempList: TStringList;
273270begin
274271 StrTempList := TStringList(Grid.Cols[4]);
275272 if StrTempList<>nil then
276273 begin
277274 ListBox1.Items.Assign(StrTempList);
278275 StrTempList.Free;
279276 end;
280277end;
281278```
282279
283280##### Notas.
284281
285282Esta propiedad funciona de una manera diferente en Lazarus respecto a Delphi cuando obtiene datos desde la grid.
286283
287284En Lazarus se crea un objeto temporal del tipo TStringList para obtener el contenido. Es responsabilidad del usuario liberar los recursos de este objeto después de su uso.
288285
289286Esto significa además que los cambios en el listado retornado no afectaran al contenido de la grid o su disposición.
290287
291288Ver el ejemplo "obtener".
292289
293290#### property Rows\[index: Integer\]: TStrings read GetRows write SetRows;
294291
295292Get/set a list of strings from/to the given grid's row index starting from column index 0 to column ColCount-1.
296293
297294##### Notas.
298295
299296Esta propiedad funciona de forma diferente en Lazarus que en Delphi cuando se cogen datos de la grid. En Lazarus se crea un objeto temporal tipo TStringList para recabar el contenido de la fila. Es responsabilidad del usuario liberar este objeto después de finalizar su uso. Esto significa también que los cambios en el listado retornado no afectarán al contenido de la grid o su disposición.
300297
301298##### Ejemplos
302299
303300- Ejemplo para establecer: Establece el contenido de la tercera fila en la grid desde una ListBox:
304301
305302``` pascal
306303Grid.Rows[2] := ListBox1.Items;
307304```
308305
309306- Ejemplo para obtener: Establece el contenido de una Listbox desde la columna índice 4 de la grid:
310307
311308``` pascal
312309
313310procedure TForm1.FillListBox1;
314311var
315312 StrTempList: TStringList;
316313begin
317314 StrTempList := TStringList(Grid.Rows[4]);
318315 if StrTempList<>nil then
319316 begin
320317 ListBox1.Items.Assign(StrTempList);
321318 StrTempList.Free;
322319 end;
323320end;
324321```
325322
326323- Un ejemplo que no funciona, y su solución: El listado de cadenas es de solo lectura.
327324
328325``` pascal
329326
330327// Esto no funcionará y causará una pérdida de memoria.
331328// porque el StringList retornado no se libera.
332329
333330Grid.Rows[1].CommaText := '1,2,3,4,5';
334331Grid.Rows[2].Text := 'a'+#13#10+'s'+#13#10+'d'+#13#10+'f'+#13#10+'g';
335332
336333// Solucionando el primer caso.
337334
338335Lst:=TStringList.Create; // Crea el StringList asignándole recursos de memoria.
339336Lst.CommaText := '1,2,3,4,5';
340337Grid.Rows[1] := Lst;
341338Lst.Free; // Libera los recursos de memoria asignados a StringList cuando ya no lo necesitamos.
342339```
343340
344341#### Propiedad UseXORFeatures;
345342
346343Propiedad booleana, su valor por defecto es False;
347344
348345Esta propiedad controla como aparece en la grid el rectángulo punteado de foco. Cuando vale **true**, el rectángulo se pinta utilizando la operación de rasteado XOR. Esto nos permite ver el rectángulo de focus sin preocuparnos del color de fondo que tengan las celdas. Cuando vale **false**, el usuario puede controlar el color del rectángulo punteado del foco utilizando la \[\[FocusColor property\] Propiedad Focus Color\].
349346
350347Esto controla además el aspecto del redimensionado fila/columna. Cuando vale **true** una línea muestra visualmente el tamaño que tendrá la fila o la columna si el usuario finaliza la operación. Cuando vale **false**, entonces el redimensionado de la fila o la columna tendrá efecta tal como el usuario arrastre el ratón.
351348
352349### TValueListEditor
353350
354351TValueListEditor es un control derivado de TCustomStringGrid para edición de pares Key-Value.
355352
356353#### Propiedad DisplayOptions
357354
358355Controla varios aspectos de la aparariencia de TValueListEditor.
359356
360357#### Propiedad TitleCaptions
361358
362359Establece los valores de los captions del título (si doColumnTitles se encuentra en DisplayOptions).
363360
364361Si DisplayOptions carece de vakir doColumnTitles entonces se utilizan los captions por defecto.
365362
366363#### Propiedad Strings
367364
368365Provee acceso al listado de cadenas que alberga los pares Key-Value.\
369366Los pares Key-Value deben encontrarse en el formulario:\
370367'KeyName=Value'
371368
372369#### Propiedad ItemProps
373370
374371Se puede utilizar esta propiedad para controlar como se pueden editar los elementos en las columnas "Value"\
375372Se controla mediante el establecimiento de las propiedades ItemProp's EditStyle y ReadOnly.
376373
377374#### Propiedad KeyOptions
378375
379376KeyOptions es un conjunto de TKeyOptions que controla de que manera el usuario puede modificar los contenidos de la columna "Key".
380377
381378- KeyEdit: el usuario puede editar el nombre y la key.
382379- KeyAdd: el usuario puede añadir keys (presionando Insert en la grid). KeyAdd requiere KeyEdit.
383380- KeyDelete: el usuario puede borrar pares Key-Value (presionando Ctrl+Delete).
384381- KeyUnique: si se establece, entonces las Keys deben tener nombres únicos. Un intento de entrar una key duplicada generará una excepción.
385382
386383#### Propiedad DropDownRows
387384
388385Si el editor de celdas es un picklist (ValueEdit1.ItemProps\['key1'\].EditStyle=esPickList) entonces esta propiedad establece DropDownCount desde el listado mostrado. El valor por defecto es 8.
389386
390387#### Función DeleteRow
391388
392389Borra el par Key-Value de la fila indexada eliminando la fila completamente.
393390
394391#### Función InsertRow
395392
396393Inserta una fila en la grid y establece el par Key-Value. Retorna el índice de la nueva fila insertada.
397394
398395#### Función IsEmptyRow
399396
400397Retorna **true** si las celdas de la fila insertada están vacias (Keys\[aRow\]=*; Values\[aRow\]=*).
401398
402399#### Function FindRow
403400
404401Retutns the row that has the specified key name.
405402
406403#### Funcion RestoreCurrentRow
407404
408405Deshace la edición en la fila actual (si el editor está todavía enfocado). Sucede cuando el usuario presiona la tecla Escape (Esc).
409406
410407#### Comportamiento alterada de algunas propiedades derivado de TCustomStringGrid
411408
412409##### Opciones de propiedades
413410
414411Debido a la naturaleza de TValueListEditor sus opciones tienen ciertas restricciones:
415412
416413- goColMoving no está permitida en Options (no puedes establecerla).
417414- goAutoAddRows solamente se puede establecer en caso de que KeyAdd se encuentre en KeyOptions. Al establecer KeyAdd automáticamente establecerá goAutoAddRows.
418415- goAutoAddRowsSkipContentCheck no está permitida (por tiempo siendo así, causará un crash en TValueListeditor: necesita ser solucionado).
419416
420417##### Propiedad FixedRows
421418
422419Puede valer solamente **1** (mostrar los títulos de las columnas) o **0** (no mostrar los títulos de las columnas).
423420
424421##### Propiedad ColCount
425422
426423Es siempre 2.
427424
428425#### Comentarios generales sobre el uso de TValueListEditor
429426
430427Cuando se manipulan los contenidos de ValueListEditor (la grid), es recomendable manipular las propiedades Strings subyacentes.\
431428Si se necesita insertar o borrar filas, entonces cualquiera de los dos realiza esto accediendo directamente a las Strings, o utilizando los métodos públicos de TValueListEditor: DeleteRow(), InsertRow(), MoveRow() and ExchangeRow().\
432429Si se intenta utiliza métodos ancestros para manipular las filas o las columnas (e.g. Columns.Add) puede desencadenar un crash.
433430
434431## Trabajando con grids
435432
436433### Personalizando grids
437434
438435Las grid (rejillas) son componentes derivados de la clase [TCustomControl](http://lazarus-ccr.sourceforge.net/docs/lcl/controls/tcustomcontrol.html), y no tienen un widget nativo asociado, lo cual significa que las grids no se encuentran restringidas por el aspecto del tema que tenga el interface en ese momento. Esto puede suponer tanto una ventaja como una desventaja: habitualmente los programadores necesitan crear un aspecto visual uniforme en las aplicaciones. Las buenas noticias son que las grids de Lazarus son suficientemente flexibles para tomar características de ambos mundo; los programadores pueden crear facilmente grids con aspecto similar a otros controles nativos, o las pueden personalizar al detalle más fino por lo que pueden obtener casi el mismo aspecto en cualquier plataforma o interface widget (esto es, con la excepción de las barras de desplazamiento (scrollbars), porque su aspecto viene todavía determinado por el tema en uso).
439436
440437### Propiedades y Eventos para personalizar las grids
441438
442439Algunas propiedades pueden afectar la forma en que se visualiza la grid actuando cuando la celda está apunto de ser pintada en PrepareCanvas/OnPrepareCanvas cambiando las propiedades por defecto del canvas (lienzo) tales como **brush** **color** o **font**. Following is a list of such properties:
443440
444441- **AlternateColor.** Con esto el usuario puede cambiar el color de fondo por uno que sirve para alternar de fila en fila de forma que sea más legible el contenido. En el dibujo de debajo el valor de **AlternateColor:=clLime;** que nos da una alternancia verdosa.
445442
446-<figure>
447443<img src="grids_alternatecolor.png" title="grids_alternatecolor.png" width="350" />
448-<figcaption>grids_alternatecolor.png</figcaption>
449-</figure>
450444
451445- **Color.** Establece el color primario para dibujar el fondo de las celdas no fijas. En el dibujo de encima en color blanco **Color:=clWhite**.
452446
453447- **FixedColor.** Este es el color utilizado para dibujar el fondo de las celdas fijas. En el dibujo de arriba que tiene la primera fila de títulos fija y la primera columna también fija FixedColor:=clSkyBlue; les da un color azulado.
454448
455449- **Flat.** Con **Flat:=True;** la grid tiene un aspecto plano con **Flat:=False** presenta un aspecto 3D más estilizado como en el dibujo de arriba.\
456450- **TitleFont.** Establece que fuente se utiliza para dibujar el texto de las celdas fijas de la fila de título.\
457451- **TitleStyle.** This property changes the 3D look of fixed cells, there are 3 settings:
458452 - *tsLazarus.* Este es el aspecto por defecto.
459453 - *tsNative.* Trata de establecer un aspecto conforme al tema actual del widgetset.
460454 - *tsStandard.* Este estilo le da un aspecto más contrastado, similar al de las grids de Delphi.
461455
462-<figure>
463456<img src="grids_titlestyle.png" title="grids_titlestyle.png" width="850" />
464-<figcaption>grids_titlestyle.png</figcaption>
465-</figure>
466457
467458- **AltColorStartNormal.** Booleano. Si vale **'True**: el color de alternancia aparece siempre en la segunda fila después de las filas fijas, , la primera fila después de las filas fijas será siempre el correspondiente al valor de **color**. Si vale **False**, entonces el color por defecto se establece a la primera fila, como si no hubiese filas fijas.
468459- **BorderColor.** This sets the grid's border color used when Flat:=True and BorderStyle:=bsSingle;
469460- **EditorBorderStyle.** If set to bsNone bajo Windows los editores de celda no tendrán el borde, como en Delphi, establecer a bsSingle por defecto porque el borde puede ser específico de un tema en algunos widgetsets y para permitir un aspecto uniforme.\
470461- **FocusColor.** El color a utilizar para dibujar la celda actualmente enfocada si UseXORFeatures no está establecida, por defecto FocusColor:=clRed.
471462- **FocusRectVisible.** Turns on/off the drawing of focused cell.
472463- **GridLineColor.** Color of grid lines in non fixed area.
473464- **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*.
474465- **SelectedColor.** Color used to draw cell background on selected cells.
475466- **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.
476467- **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 dibuja el texto en cada celda.
477468
478469- **AutoAdvance.** donde irá el cursor de celdas cuando se presione enter, o después de una edición.
479470- **TabAdvance.** donde irá el cursor de celdas cuando se presione Tab o Shift-Tab.
480471- **ExtendedColSizing.** Si vale **true** entonces el usuario puede redimensionar las columnas no solamente en la cabecera sino a lo largo de toda la altura de la columna.
481472
482473Otras propiedades que también afectan al aspecto de las grids.
483474
484475**Options**.
485476
486477
487478La propiedad **Options**' es un conjunto con algunos elementos para habilitar diversas funcionalidades pero algunas están relacionadas directamente con el aspecto de la grid. Estas opciones pueden establecerse tanto en tiempo de diseño como durante la ejecución del programa.
488479
489480- **goFixedVertLine, goFixedHorzLine** dibuja respectivamente una línea vertical u horizontal delimitan celdas o columnas en un área fija, activa por defecto.
490481- **goVertLine, goHorzLine** lo mismo que la anterior, pero con un área normal visible. Se puede construir una grid que simule un listbox desestablecidendo estos dos elementos.
491482- **goDrawFocusSelected** si se habilita este elemento entonces se pinta una selección de fondo en la celda con foco en adición al rectangulo punteado con foco (ten en cuenta que esto no funciona todavía cuando la opción goRowSelect está establecida, en tal caso la fila siempre se pinta como si goDrawFocusSelected estuviese establecida).
492483- **goRowSelect** selecciona la fila completa en lugar de celdas individuales.
493484- **goFixedRowNumbering** si se establece, la grid realizará un numerado en su primera columna fija.
494485- **goHeaderHotTracking** si se establece, la grid tratará de mostrar un aspecto diferente cuando el cursor del ratón entre en el área de cualquier celda fija. Para que esto funcione, la zona de celda deseada necedita tener habilitada su propiedad HeaderHotZones. Prueba a combinar esta opción con la propiedad TitleStyle:=tsNative para obtener un aspecto themed hot tracking.
495486- **goHeaderPushedLook** si se establece, este elemento habilita un aspecto de pulsado cuando se hace click en una celda fija. La zona de celda "pulsable" se habilita utilizando la propiedad HeaderPusedZones.
496487
497488(escribir más contenido)
498489
499490### Descripción del proceso de dibujado de las grids
500491
501492Al igual que otros controles personalizados, la grid se dibuja utilizando el método paint. En términos generales la grid se dibuja pintando todas las filas y a su vez cada fila pintando sus celdas individuales.
502493
503494El proceso es el siguiente:
504495
505496- Primeramente se determina el área de las celdas visibles: cada fila es testeada para ver si intersecta con la región de recorte del canvas (lienzo de dibujo), caso de ser así entonces el área visible se pinta dibujando las columnas de cada fila.
506497- A continuación se utilizan los valores de columna y fila para identificar la celda que va a ser pintada y de nuevo se testea cada columnapor la intersección con la región de recorte; si todo va bien, entonces se pasan como argumentos al método DrawCell algunas propiedades adicionales tales como la extensión rectangular de la celda.
507498- Según avanza el proceso de dibujado se va ajustando el estado visual de cada celda de acuerdo a las opciones y posiciones dentro de la grid. El estado visual se retiene en una variable tipo TGridDrawState que es un conjunto de los siguientes elementos:
508499 - *gdSelected* La celda tendrá un aspecto de seleccionada.
509500 - *gdFocused* La celda tendrá un aspecto de enfocada.
510501 - *gdFixed* La celda tendrá que ser pintada con aspecto de celda fija.
511502 - *gdHot* El cursor del ratón se encuentra sobre esta celda, por tanto se pintará la celda con aspecto hot tracking.
512503 - *gdPushed* La celda esta siendo clickeada, por tanto se pinta con aspecto de haber sido pulsada.
513504- **DrawCell.** El método DrawCell es virtual y puede ser sustituido (overriden) en grids descendientes para realizar un dibujado personlaizado. La información pasada a DrawCell ayuda a identificar que celda en particular esta siendo dibujada, el área física ocupada en la pantalla y su estado visible. Ver la referencia de DrawCell para más detalles. Lo siguiente sucede para cada celda:
514505- **PrepareCanvas.** En este método, si se establece la propiedad DefaultDrawing , entonces el canvas de la grid se establece con las propiedades por defecto de la brocha (**brush**) y fuente (**font**) basadas en el estado visual actual. Por medio de varias propiedades (tanto en tiempo de diseño como de ejecución) se establece la alineación de texto de manera que coincida con la selección del programador en cuanto a las columnas personalizadas si es que existen. En caso de que DefaultDrawing valga false, el color de la brocha (**brush**) se estable para que valga clWindow y el color de fuente clWindowText, además el valor de la propiedad defaultTextStyle de las grids se utiliza para establecer la alineación de texto.
515506- **OnPrepareCanvas.** si el programador escribe un manejador de evento para el evento OnPrepareCanvas entonces este es llamado en este punto. Este evento se puede utiliar para realizar personalizaciones simples tales como cambiar el color de fondo de las celdas, propiedades de las fuentes como el color, tipografía y estilo, distribución del texto como son sus diferentes combinaciones de alineación: izquierda (**left**), centro (**center**), arriba del todo (**top**), al fondo (**botton**) o derecha (**right**), etc. Cualquier cambio realizado al canvas en este evento se perdería porque el siguiente dibujado de celda reseteará el canvas de nuevo a su estado por defecto. Por lo tanto es seguro realizar cambios únicamente para una celda o celdas en particular y olvidarse de esto para el resto. Utilizar este evento ayuda algunas veces a evitarel evento OnDrawCell de las grid, donde los usuarios se verían forzados a duplicar el código de las grids. Por hacer: ¿ejemplos de que puede ser realizado o que no para OnDrawCell?...
516507- **OnDrawCell.** Lo siguiente, si no se ha especificado un manejador para el evento OnDrawCell, es que la grid llama al método DefaultDrawCell, el cual simplemente pinta el fondo de la celda utilizando para ello el color y estilo del brush para canvas actual. Si el manejador OnDrawCell existe entonces la grid pinta primeramente el fondo de la celda pero solamente si la propiedad defaultDrawing está establecida. a continuación llama al evento OnDrawCell para realizar un pintao personalizado. Habitualmente los programadores necesitan realizar un dibujado personalizado solamente para celdas concretas y el estandar para otras; en este caso lo que pueden hacer es restringir la operación personalizada a cierta celda/s mirando denttro de los argumentos **ACol**, **ARow** y **AState** mientras que para las otras celdas simplemente llaman al método DefaultDrawCell y dejan a la grid que se encargue de ello.
517508- **Text.** En este punto (solamente para TStringGrid) si la propiedad DefaultDrawing vale **True**, realiza el pintado del texto de la celda.
518509- **Grid lines**. El último paso para cada celda consist en pintar las líneas de la grid: si se especifican las opciones de grid **goVertLine**, **goHorzLine**, **goFixedVertLine** y **goFixedHorzLine** entonces ya se realiza el pintado de la celda. Se pueden obtener grids con únicamente filas o columnas cambiando estas opciones. Si el programador escoge tener un aspecto "themed" esto también se realiza en este punto (ver propiedad **TitleStyle**).
519510- **FocusRect.** Cuando ya se han pintado todas las columnas de la fila actual llega el momento de realizar el dibujado del rectángulo de foco para la celda actualmente seleccionada o la fila completa en caso de que la opción goRowSelect se encuentre establecida..
520511
521512#### Diferencias con Delphi
522513
523514- En Lazarus el método TCustomGrid.DrawCell no es abstracto y su implementación por defecto realiza el rellenado básico del fondo de la celda.
524515- En Delphi, el texto de la celda se dibuja antes del evento OnDrawCell (Ver [bug report \#9619](http://www.freepascal.org/mantis/view.php?id=9619)).
525516
526517### Selección de celdas de la grid
527518
528519La localización de la celda (o fila) actual (enfocada) se puede cambiar utilizando el teclado, el ratón o mediante código. Para cambiar con éxito el foco de una celda a otra posición, debemos comprobar previamente para ver si la posición destino lo admite. Cuando se utiliza el teclado, la propiedad AutoAdvance realiza parte del proceso encontrando cual será la siguiente celda enfocada. Cuando se utiliza el ratón para clickar en la celda o bien mediante código, no se moverá el foco desde la celda actual a menos que la celda destino permita obtener dicho foco.
529520
530521La grid llama a la función **SelectCell** para ver si una celda es enfocable: si esta función retorna **True**, entonces la celda destino identificada con los argumentos **aCol** y **aRow** es enfocable (la implementación actual de TCustomGrid simplemente retorna **true**). TCustomDrawGrid y de ahí TDrawGrid y TStringGrid sobreescriben este método para chequear primero si la celda tiene un ancho diferente de cero; normalmente no necesitas una celda seleccionada con un ancho de cero por lo que una celda con estas características es descartada automáticamente en el proceso de encontrar una celda adecuada. La otra cosa que el método SelectCell reemplaza es llamar al evento de usuario configurable OnSelectCell: este evento recibe las coordenadas de la celda como argumentos y siempre retorna una valor por defecto de **true**.
531522
532523Una vez que se sabe que una celda es enfocable y que el movimiento tendrá lugar, primeramente se llama al método BeforeMoveSelection; este en turnos lanzará el eventoOnBeforeSelection. Los argumentos de este método son las coordenadas de la nueva celda enfocada. En este punto cualquier editor visible se oculta también. La palabra "before" significa que la selección no es cambiada todavía y las coordenadas del foco pueden ser accedidas mediante las propiedades **grid.Col** y **grid.Row**.
533524
534525After 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).
535526
536527Ten en cuenta que no es bueno utiliza el evento OnSelectCell para detectar cambios en el foco de la celda, ya que este evento se lanzará varias veces para la misma celda en el proceso de encontrar una celda adecuada. Es mejor utilizar los enventos OnBeforeSelection o OnSelection para este propósito.
537528
538529#### Diferencias con Delphi
539530
540531- El comportamiento de SelectCell y OnSelectCell es probablemente diferente - no puedo realmente hacer un comentario sobre las diferencias. En Lazarus se utilizan en funcionalidades tales como AutoAdvance la cual que yo sepa probablemente no existe en Delphi.
541532
542533### Cuando las propiedades de base que trae la grid no son suficiente: grids derivadas
543534
544535Las grids derivadas usualmente tienen que sobreescribir los siguientes métodos:\
545536DrawAllRows: Dibuja todas las filas visibles.\
546537DrawRow: Dibuja todas las celdas en una fila.\
547538DrawRow dibuja todas las celdas de una fila chequeando primeramente si la celda se encuentra en la región del recorten, y dibujando solamente si se cumple dicha situación.\
548539DrawCell:\
549540DrawCellGrid:\
550541DrawCellText:\
551542DrawFocusRect:\
552543(ampliar esta sección con más contenido).\
553544
554545### ¿Qué sucede en el método `TCustomGrid.Paint`
555546
556547El siguiente listado es para mostrar el orden interno de llamadas a métodos de pintado de un `TCustomGrid` (o descendiente). Cada uno de los elementos en el listado representan llamadas a métodos durante la operación de pintado. Esto debería ayudar a encontrar el punto correcto para cambiar su comportamiento cuando esto viene a descender de `TCustomGrid`.
557548
558549- **`DrawEdges`**: Dibuja el borde más externo de la grid.
559550- **`DrawAllRows`** (virtual): Dibuja todas las filas en la parte visible de la grid. Este es el único método que se llama en el proceso de dibujado que es declarado *`virtual`*.
560551 - **`DrawRow`** (virtual): Se llama para cada fila dentro de la actual ventana de visualización.
561552 - **`DrawCell`** (virtual): Se llama primeramente para cada celda 'normal' (i.e. no fija) en la fila.
562553 - **`PrepareCanvas`** (virtual): Establece los estilos de dibujados del canvas de acuredo a las propiedades visuales actuales de las celdas.
563554 - **`DrawFillRect`**: Dibuja el fondo de las celdas con los estilos establecidos en `PrepareCanvas`.
564555 - **`DrawCellGrid`** (virtual): Dibuja las líneas de borde de las celdas.
565556 - **`DrawFocusRect`** (virtual): En `TCustomGrid`este método no hace nada. En las grids descendientes este método se utiliza para dibujar el rectángulo de foco dentro de la celda activa.
566557 - **`DrawCell`** (virtual): (ver acerca de) Se llama para cada celda fija en la línea visible dentro de ventana de visualización.
567558- **`DrawColRowMoving`**: Se encuentra activa solamente mientras se mueve una columna o una fila. Este método dibuja la nueva posición de las filas/columnas.
568559- **`DrawBorder`**: Si se necesita (`Flat=TRUE` y `BorderStyle=bsSingle`), esto dibuja una línea de borde interna.
569560
570561#### Métodos adicionales para dibujar en `TCustomGrid`
571562
572563Estos métodos son declarado y (parcialmente) implementados en `TCustomGrid`, pero no son llamados directamente desde aquí. Se utilizan por parte de las clases descendientes para dibujar el contenido de las celdas.
573564
574565- **`DrawCellText`** (virtual): Escribe/dibuja el texto,que se pasa como parámetro, dentro de la celda. El texto se formatea utilizando los estilos que están activos en `Canvas.TextStyle` (ver también `PrepareCanvas`).
575566
576567- **`DrawThemedCell`** (virtual): Se utiliza únicamente para las celdas fijas y si `TitleStyle=tsNative`. Dibuja el fondo de las celdas utilizando `ThemeServices`.
577568
578569- **`DrawColumnText`** (virtual): Se utiliza únicamente en las cabeceras de las columnas de las celdas.
579570 - **`DrawColumnTitleImage`**: Si la grid está como un `TitleImageList` asignado y si `Columns.Title[x].Title.ImageIndex` contiene un valor válido, esta imagen es dibujada dentro de la celda.
580571 - **`DrawCellText`** : (ver mas sobre esto).
581572
582573- **`DrawTextInCell`** (virtual): Se utiliza para las celdas 'normal' (i.e. no para las fijas). En `TCustomGrid` este método no hace nada. En las grids descendientes este método se utiliza para dibujar el contenido de las celdas. En `TStringGrid` este método llama a `DrawCellText` (ver más sobre esto).
583574
584575#### Métodos de dibujado introducidos por `TCustomDrawGrid`
585576
586577Como puedes ver la clase base `TCustomGrid` no dibuja contenido dentro de las celdas. Esto es realizado en: **`TCustomDrawGrid`**. **`TCustomDrawGrid`** overrides the method `DrawCell` del modo siguiente:
587578
588579- **`DrawCell`** (override):
589580 - **`PrepareCanvas`**: llama al método heredado desde **`TCustomGrid`**.
590581 - **`DefaultDrawCell`** (virtual): Este método se llama solamente si el manejador de eventos para `OnDrawCell` NO está asignado.
591582 - **`DrawFillRect / DrawThemedCell`** (de `TCustomGrid`): Dibuja el fondo de las celdas. Si `TitleStyle=tsNative`, entonces el fondo de las celdas fijas se dibuja utilizando `DrawThemedCell`.
592583 - **`DrawCellAutonumbering`** (virtual): Si `goFixedAutonumbering en Options`, entonces el número de filas se dibujan dentro de la primera columna fija utilizando `TCustomGrid.DrawCellText`.
593584 - **`DrawColumnText`** (desde `TCustomGrid`): Solamente se llama para celdas de cabecera de columna (ver *Additional methods to draw in `TCustomGrid`*).
594585 - **`DrawTextInCell`** (desde `TCustomGrid`): Solamente se llama para celdas 'normales' (i.e. no fijas) (ver *Additional methods to draw in `TCustomGrid`*).
595586 - **`DrawCellGrid`** (virtual): Dibuja los bordes de las celdas.
596587
597588### Salvar y recuperar el contenido de una Grid
598589
599590Los métodos **SaveToFile** y **LoadFromFile** permiten salvar y recuperar tanto la disposición como el contenido hacia/desde un fichero con formato XML. TStringGrid, como heredero de TCustomStringGrid, tiene también la habilidad de "exportar" e "importar" su contenido hacia/desde un fichero con formato de valores separados por comas, mejor conocido como ficheros CSV (Comman Separated Values). Esto es descrito en la referencia de los métodos **SaveToCSVFile** y **LoadFromCSVFile** (POR HACE: crear enlaces).
600591
601592El tipo de información que puede ser salvada y después recuperada utilizando **SaveToFile** y **LoadFromFile** viene determinada por la propiedad SaveOptions (del tipo TSaveOptions) la cual es un conjunto de opciones descritas como sigue:
602593
603594soDesign: Salvar & cargar ColCount, RowCount, FixedCols, FixedRows, ColWidths, RowHeights y Options (TCustomGrid)
604595
605596soPosition: Salvar & cargar Posición de Scroll, Row, Col y Selection (TCustomGrid)
606597
607598soAttributes: Salvar & cargar Colores, Alineación de texto & Disposición, etc. (TCustomDrawGrid)
608599
609600soContent: Salvar & cargar texto (TCustomStringGrid)
610601
611602soAll: Establecer todas las opciones (soAll:=\[soDesign,soPosition,soAttributes,soContent\];)
612603
613604No todas las opciones se aplican a todos los tipos de grids, por ejemplo, soContent no se aplica a las grids derivadas de TCustomGrid como TDrawGrid ya que este tipo de grids no tiene el concepto de "contenido". TStringGrid es un tipo de grid especial que "conoce" como manejar cadenas (strings) y que puede utilizar la opción soContent en caso de que se especifique.
614605
615606La opción soAttributes no se utiliza en las grids standar de Lazarus, se encuentra ahí para las grids derivadas.
616607
617608Cuando se utiliza **LoadFromFile** la grid también utiliza la propiedad SaveOptions para conocer que tipo de información necesita recuperar desde el fichero, por lo que es perfectamente posible especificar SaveOptions:=\[soDesign,soContent\] al salvar y solamente SaveOptions:=\[soContent\] en la carga. Para una TStringGrid el valor por defecto para SaveOptions es \[soContent\], para otros tipos de grids, SaveOptions es un conjunto vacío..
618609
619610**Nota:**
620611
621612Una incidencia común tiene lugar cuando se salvan & recuperan datos de la grid al especificar el usuario la propiedad SaveOptions antes que la propiedad **SaveToFile** pero no antes de **LoadFromFile**. Cuando se utiliza **LoadFromFile** algún tiempo después de que se ha utilizado **SaveToFile**, entonces la propiedad SaveOptions property se establece apropiadamente, pero si se ejecuta **LoadFromFile** en la próxima ejecución del programa, la propiedad SaveOptions property puede que no haya sido establecida correctamente, por esta razón es recomendable especificar siempre la propiedad SaveOptions justamente antes de **LoadFromFile**, o haciendo esto mismo globalmente al comienzo del programa como en el siguiente ejemplo:
622613
623614------------------------------------------------------------------------
624615
625616**Ejemplo:**
626617
6276181. Primeramente, ve al menú en "Fichero -\> Nuevo -\> Proyecto -\> Application";
6286192. Emplaza un componente TStringGrid en el formulario (lo veremos como StringGrid1);
6296203. Emplaza un componente TButton y otro TOpenDialog en el forumulario (los veremos como Button1 y OpenDialog1);
6306214. Añade un evento OnCreate al formulario (tendrá lugar cuando se cree el forumulario).
6316225. añade un evento OnClick para el button (tendrá lugar cuando cuando hagamos click sobre el pulsador Button1).
632623
633624``` pascal
634625unit Unit1;
635626
636627{$mode objfpc}{$H+}
637628
638629interface
639630
640631uses
641632 Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Grids,
642633 Buttons, StdCtrls, XMLCfg;
643634
644635type
645636
646637 { TForm1 }
647638 TForm1 = class(TForm)
648639 StringGrid1: TStringGrid;
649640 Button1: TButton;
650641 OpenDialog1: TOpenDialog;
651642 procedure Button1Click(Sender: TObject);
652643 procedure Form1Create(Sender: TObject);
653644 private
654645 { private declarations }
655646 public
656647 { public declarations }
657648 end;
658649
659650var
660651 Form1: TForm1;
661652
662653implementation
663654
664655{ TForm1 }
665656
666657procedure TForm1.Form1Create(Sender: TObject);
667658begin
668659 //sets the SaveOptions at creation time of the form
669660 stringgrid1.SaveOptions := [soDesign,soPosition,soAttributes,soContent];
670661end;
671662
672663procedure TForm1.Button1Click(Sender: TObject);
673664begin
674665 //Pregunta si se ha lanzado el método Execute de OpenDialog1
675666 //de forma que cuando esto suceda, el usuario selecciona el fichero .XML a cargar
676667 //cuyo nombre ha sido almacenado en la propiedad FileName.
677668
678669 if opendialog1.Execute then
679670 Begin
680671 //Limpia la grid.
681672 StringGrid1.Clear;
682673 //Carga desde el fichero .XML seleccionado la grid.
683674 StringGrid1.LoadFromFile(OpenDialog1.FileName);
684675 //Refresca la Grid.
685676 StringGrid1.Refresh;
686677 End;
687678end;
688679
689680initialization
690681 {$I unit1.lrs}
691682
692683end.
693684```
694685
695686------------------------------------------------------------------------
696687
697688**Fichero de ejemplo .XML:** (Copia el texto de debajo dentro de un fichero tipo texto (txt). No olvides poner la cabecera definitoria xml :-))
698689
699690``` xml
700691<?xml version="1.0"?>
701692<CONFIG>
702693 <grid version="3">
703694 <saveoptions create="True" position="True" content="True"/>
704695 <design columncount="2" rowcount="5" fixedcols="1" fixedrows="1" defaultcolwidth="64" defaultRowHeight="20">
705696 <options>
706697 <goFixedVertLine value="True"/>
707698 <goFixedHorzLine value="True"/>
708699 <goVertLine value="True"/>
709700 <goHorzLine value="True"/>
710701 <goRangeSelect value="True"/>
711702 <goDrawFocusSelected value="False"/>
712703 <goRowSizing value="False"/>
713704 <goColSizing value="False"/>
714705 <goRowMoving value="False"/>
715706 <goColMoving value="False"/>
716707 <goEditing value="False"/>
717708 <goTabs value="False"/>
718709 <goRowSelect value="False"/>
719710 <goAlwaysShowEditor value="False"/>
720711 <goThumbTracking value="False"/>
721712 <goColSpanning value="False"/>
722713 <goRelaxedRowSelect value="False"/>
723714 <goDblClickAutoSize value="False"/>
724715 <goSmoothScroll value="True"/>
725716 </options>
726717 </design>
727718 <position topleftcol="1" topleftrow="1" col="1" row="1">
728719 <selection left="1" top="1" right="1" bottom="1"/>
729720 </position>
730721 <content>
731722 <cells cellcount="10">
732723 <cell1 column="0" row="0" text="Title Col1"/>
733724 <cell2 column="0" row="1" text="value(1.1)"/>
734725 <cell3 column="0" row="2" text="value(2.1)"/>
735726 <cell4 column="0" row="3" text="value(3.1)"/>
736727 <cell5 column="0" row="4" text="value(4.1)"/>
737728 <cell6 column="1" row="0" text="Title Col2"/>
738729 <cell7 column="1" row="1" text="value(1.2)"/>
739730 <cell8 column="1" row="2" text="value(2.2)"/>
740731 <cell9 column="1" row="3" text="value(3.2)"/>
741732 <cell10 column="1" row="4" text="value(4.2)"/>
742733 </cells>
743734 </content>
744735 </grid>
745736</CONFIG>
746737```
747738
748739------------------------------------------------------------------------
749740
750741--Raditz 21:06, 11 Jan 2006 (CET) desde **ARGENTINA**
751742
752743### Editores de celdas de la grid.
753744
754745La grid utiliza editores de celda para cambiar el contenido de las celdas.
755746
756747Para una grid especializada como TStringGrid, el editor es el usual control editor de simple línea, pero algunas veces es deseable tener otros medios de entrar información. Por ejemplo:
757748
7587491. Mostrar el diálogo de apertura de fichero para encontrar la ubicación de un fichero de manera que el usuario no tenga que teclear el trayecto completo de forma manual.
7597502. So eñ texto en la celdai representa una fecha, presentar un calendario emergente de forma que se pueda especificar una fecha facilmente.
760751
761752Algunas veces la información que el usuario debe entrar en una celda está limitada a una lista de palabras; es este caso teclear la información directamente puede inducir a errores y por tanto aparece la necesidad de implementar rutinas de validación. Esto lo podemos evitar utilizando un editor de celda que presente al usuario una lista conteniendo solamente valores legales.
762753
763754Este es también el caso para grids genéricas como TDrawGrid donde el usuario necesita algún tipo de estructura para albergar los datos que serán mostrados en la grid. En esta situación, la información que es entrada en el editor de celda, actualiza la estructura interna para reflejar los cambios en la grid.
764755
765756#### Editores de celdas integrados
766757
767758La unit grids.pas incluye algunos de los editores más utilizados listos para usar en las grids. Es también posible crear nuevos editores de celda (editores de celdas personalizados) en caso de que los integrados no sean suficiente para una tarea específica.
768759
769760Los editores de celdas integrados son Button, Edit, y Picklist.
770761
771762#### Utilizando editores de celdas
772763
773764Los usuarios pueden especificar que editor se utilizará para las celdas escogiendo uno de estos dos métodos.
774765
7757661. **Utilizando una columna personalizada y seleccionando la propiedad ButtonStyle de la columna**. En este método el usuario puede seleccionar el estilo del editor que se mostrará. Los valores disponibles son: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn.
7767672. **Utilizando el evento de la grid OnSelectEditor**. Aquí el usuario especifica en el parámetro **Editor** que editor se utilizará para una celda identificada por aCol y row ARow en una grid derivada de TCustomDrawGrid o TColumn en TCustomDBGrid. Para este propósito resultan útil una función pública de las grids, EditorByStyle(), que toma como parámetro uno de los siguientes valores: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn. Este método tiene precedencia sobre el primero utilizando columnas personalizadas. Aquí se puede especificar un editor de celdas personalizado. Este evento es además el lugar para establecer el editor con valres específicos de la celda, fila o columna (e.g. un popupmenu personalizado para el editor de celdas).
777768
778769Establecer la propiedad *ButtonStyle* solamente funcionará si la columna se crea con la sentencia *StringGrid1.Columns.Add;*. En caso de utilizar una expresión tal como *StringGrid1.ColCount:=X;* se originará una excepción. Para establecer la propiedad ButtonStyle se puede hacer con un código similar:
779770
780771``` pascal
781772
782773if ColCB < StringGrid1.Columns.Count then StringGrid1.Columns.Items[ColCB].ButtonStyle:=cbsCheckboxColumn;
783774```
784775
785776#### Descripción de estilos del editor
786777
787778La siguiente es una descripción de los estilos de editor. Se enumeran valores de tipo TColumnButtonStyle por lo que utiliza el prefijo 'cbs'. Este tipo se ha utilizado para mantener las compatibilidad con DBGrid de Delphi.
788779
789780- **cbsAuto**
790781
791782
792783Este es el estilo por defecto del editor para grids derivadas de TCustimGrid. La clase actual del editor que se utilizará para editar el contenido de las celdas dependerá de varios factores.Para TCustomGrids utiliza una clase TStringCellEditor derivada de TCustomMaskEdit. Este editor está especializado en la edición de cadenas de una sola línea. Es por tanto utilizado por defecto en TStringGrid y TDrawGrid. Cuando se utilizan columnas personalizadas, si el programador rellena Column's PickList apropiadamente, entoces se comporta como si el estilo del editor cbsPickList estuviese establecido. Para una TCustomDBGrid que tenga un campo de tipo booleano, se comportará como si el estilo del editor estuviese establecido a cbsCheckBoxColumn. Este es el valor recomendado para Custom Cell Editors. Por hacer: OnEditingDone relacionado.
793784
794785- **cbsEllipsis**
795786
796787
797788Este estilo de editor es el más genérico. Cuando se utiliza, aparece un button en la celda de edición de manera que los programadores pueden utilizar el evento OnEditButtonClick de la grid para detectar cuando el usuario ha presionado el button y por tanto tomar una acción programada para esa celda. Por ejemplo, un programador puede utilizar este estilo de editor para presentar un diálogo emergente tipo calendario para permitir al usuario seleccionar una fecha específica fácilmente. Otras posibilidades podrían ser mostrar un diálogo de apertura de fichero para encontrar ficheros, una calculadora de forma que el usuario pueda introducir el resultado numérico del cálculo, etc.
798789
799790
800791OnEditButtonClick Es justamente una notificación, para encontrar en que celda se ha clickeado el button mediante la lectura de las propiedades grid.Row y grid.Col.
801792
802793
803794Una DBGrid tiene propiedades específicas para recuperar la columna activa o campo y dado que este evento tiene lugar en el campo activo, entonces puede actualizar la información que contiene
804795
805796
806797Este estilo de editor se implementa utilizando TButtonCellEditor, un descendiente directo de TButton.
807798
808799- **cbsNone**
809800
810801
811802Este estilo de editor instruye a la grid para que no usue editores para una celda o columna específica; esto se comporta como si la grid fuese de solo lectura para dicha celda o columna.
812803
813804- **cbsPickList**
814805
815806
816807Utilizado para presentar al usuario una lista de valores que pueden ser entrados. Este estilo de editor se implementa utilizando TPickListCellEditor, un componente derivado de TCustomComboBox. El listado de valores que se muestran se llena en uno o dos sentidos dependiendo del método utilizado para seleccionar el estilo de editor.
817808
8188091. Cuando se utilizan columnas personalizadas, los programadores pueden introducir un listado de valores utilizando la propiedad de columnas PickList. \[FOR BEGINNERS: TODO: exact procedure to edit the list\]
8198102. En OnSelectEditor, los programadores obtienen la instancia TPickListCellEditor utilizando la función EditorByStyle(cbsPickList). [[Grids Reference Page|Ver aquí para un ejemplo]]
820811
821812`var Lst:TPickListCellEditor; `\
822813`  begin [...] `\
823814`    Lst:=TPickListCellEditor(EditorByStyle(cbsPickList)); `\
824815`    Lst.clear; `\
825816`    Lst.Items.add('One'); `\
826817`    Lst.items.add('Two'); `\
827818`    Editor:=Lst; `\
828819`  end;`
829820
830821
831822El valor en una grid tipo TStringGrid reflejará automáticamente el valor seleccionado. Si es necesario el programador puede detectar el momento en que el valor es seleccionado mediante la escritura de un manejador de evento para el evento de la grid OnPickListSelect, por lo que se pueden realizar pasos adicionales (por ejemplo, para procesar un nuevo valor). COSAS POR HACER: relacionado con OnEditingDone.
832823
833824- **cbsCheckboxColumn**
834825
835826
836827Puede resulta útil cuando los datos contenidos asociados con la columna se encuentran restringidos a un par de valores, por ejemplo, yes-no, true-false, 1-0, etc. En lugar de forzar al usuario a teclear los valores para estos tipos de datos en un StringCellEditor o escoger de un listado, se usa cbsCheckboxColumn para modificar los datos de una columna mediante el uso de un checkbox en el que el usuario puede facilmente conmutar los valores con un simple click de ratón o presionando la tecla ESPACIADOR (si la columna que contiene el checkbox se encuentra seleccionada y además el StringGrid se encuentra en la modalidad de edición).
837828
838829Obtener o establecer el valor de un checkbox en una celda se realiza de la siguiente manera:
839830
840831``` pascal
841832StringGrid1.Cell[x,y]:='Z';
842833```
843834
844835
845836donde Z debe ser reemplazado por *o* si no marcado, *1* para marcado y una cadena vacío si deshabilitado en color gris (Grayed). Ten en cuenta que cualquier valor (string) diferente de *0* y *1* se mostrará como un checkbox de color gris claro (deshabilitado).
846837
847838
848839Si la propiedad ButtonStyle de las columnas se establece a cbsAuto y DBGrid detecta que el campo asociado con la columna es un campo booleano, entoces la grid utiliza est estilo de editor automáticamente. Esta selección automática se puede deshabilitar o habilitar utilizando la propiedad OptionsExtra de las DBGrid; estableciendo el elemento dgeCheckboxColumn a false deshabilita esta característica.
849840
850841
851842Los valores que se utilizan para reconocer los estados de marcado o desmarcado se establecen en las propiedades de columnas ValueChecked y ValueUnchecked.
852843
853844
854845En cualquier momento, el valor del campo puede estar en uno de estos tres estados: Unchecked, Checked o Grayed. Internamente estos estados están identificados por los siguientes valores de tipo TDBGridCheckBoxState: gcbpUnChecked, gcbpChecked y gcbpGrayed.
855846
856847
857848Este estilo de editor no utiliza componentes reales tipo TCheckbox para manejar las interacciones de usuario la representación visual viene dada por tres imágenes bitmap internas que se corresponden con los tres posibles estados de checkbox. Los bitmaps utilizados pueden ser personalizados escribiendo un manejador para el evento OnUserCheckboxBitmap de DBGrid; el manejador de este evento obtiene el estado del checkbox del parámetro CheckedState de tipo TDBGridCheckboxState y un parámetro bitmap que el programador puede especificar para bitmaps personalizados.
858849
859850- **cbsButtonColumn**
860851
861852
862853Este estilo de editor se utiliza para mostrar un pulsador (button) en cada celda en las columnas. Como en el caso de cbsCheckboxColumn este editor no utiliza pulsadores (buttons) reales sino que su apariencia viene definida por el tema actual del WidgetSet.
863854
864855
865856El usuario sabe que pulsador en particular ha sido presionado mediante el uso del evento OnEditButtonClick de la grid y chequeando en que fila y columna se encuentra. Fíjate que en este caso en particu. Una vez que el evento OnEditButtonClick se ha gestionado, y si el usuario no ha modificado la fila o columna dentro de este manejador, entonces la grid automáticamente re-establece la columna y fila para reflejar la celda actualmente seleccionada. Mientras se gestiona el evento OnEditButtonClick la actual selección de la grid está disponible en grid.Selection, la cual es una propiedad TRect, donde left y right representan índices columna, siendo Top y Bottom indices fila.
866857
867858
868859El caption de button es la string correspondiente de la celda.
869860
870861### Editando Grids
871862
872863El efecto de edición es diferente dependiendo de que tipo de grid se esté utilizando, por ejemplo, TStringGrid almacena el texto editado internamente y TDBGrid afecta a los registros dentro del dataset (conjunto de datos). En TDrawGrid (al menos esto debería funcionar para todos las clases de Grids) para acceder al texto editado, el programador puede utilizar el evento**OnSetEditText** el cual es disparado cada vez que el usuario modifica algo en el editor, , los parámetros *aCol* y *aRow* de este evento identifican la celda que está siendo editada, el valor del parámetro *value* alberga el texto, este es el método recomendado. Otro camino para acceder al texto editado es tomarlo directamente desde el editor una vez que el proceso de edición ha finalizado utilizando el evento **OnEditingDone**. This could be accomplished by accessing the internal editor used for editing, in this case, the default "String Cell Editor". Para esto hay dos métodos: El primero es utlilizar el evento **OnSelectEditor** donde el parámetro *Editor* es la instacia que se utilizará para editar la celda, tu tiene que almacenar la instancia en una variable, por ejemplo TheEditor (de tipo *TStringCellEditor*), para posteriormente utilizar **OnEditingDone**. La segunda alternativa es utilizando el método **EditorByStyle** de las grids, este método acepta un "editor style" como parámetro y retorna la instancia del editor correspondiente a este estilo. En nuestro caso conociendo que el estilo cbsAuto retorna el editor de celdas por defecto, podemos usar directamente lo sigiente en el manejador de eventos **OnEditingDone**: TheEditor := Grid.EditorByStyle(cbsAuto) Pudiendo entonces obtener el texto con *TheEditor.Text*. Si utilizas este método ten en cuenta que "gran poder implica gran responsabilidad" antes de modificar algo.
873864
874865#### Opciones y propiedades que afectan a la edición
875866
876867El comportamiento de la edición se encuentra controlado por un cierto número de propiedades y opciones, virtualmente cualquier ajuste u opciones que afecten a la edición requieren una grid editable o tales ajustes serán ignorados.\
877868Al inicio el estado editable de las grids es diferente, para TDrawGrid y TStringGrid la edición está deshabilitada, para TDbGrid está habilitada por defecto.\
878869La propiedad **Options** tiene varios elementos que lidian con la edición, y se encuentran descritas a continuación:\
879870
880871- **goEditing, dgEditing (en DbGrid)**. Esta opción cambia el estado de edición de la grid, se puede cambiar en tiempo de ejecución porque se chequea cuando la edición está apunto de ser iniciada.
881872- **goAlwaysShowEditor**. Normalmente el editor permanece oculto y se hace visible solamente cuando se necesita. Con esta opción, el editor se hará visible todo el tiempo. En caso de que la grid no sea editable esta opción se ingnora y el editor permanece siempre oculto.
882873
883874## Como hacer y Ejemplos
884875
885876#### Enfocando una celda
886877
887878Enfocar una celda en TStringGrid es fácil. Ten en cuenta que la cuenta comienza desde 0 no desde 1, por lo que para enfocar la fila 10, columna 9 hacemos:
888879
889880``` pascal
890881StringGrid1.row := 9;
891882StringGrid1.col := 8;
892883```
893884
894885### Ejemplo: Como establecer un editor de celdas personalizado
895886
896887Ver lazarus/examples/gridexamples/gridcelleditor/gridcelleditor.lpi (desde Lazarus 1.2)
897888
898889### Ejemplo: Como establecer un editor memo para dbgrids
899890
900891Tu puedes, por supuesto, utilizar otro control en lugar de un TMemo. Ajustalo a tu gusto. Adaptado desde [1](http://forum.lazarus.freepascal.org/index.php?topic=3640.0) (simplificado para utilizar la propiedad SelectedFieldRect , ver [2](http://forum.lazarus.freepascal.org/index.php/topic,8713.msg43288.html#msg43288))
901892
902893- Emplaza un control memo o cualquier control que necesites) en tu formulario, establece las propiedades y visible a false. Esto se utilizará cuando se edite una celda de la grid. Nosotros utilizaresmos GridCellMemo en este ejemplo.
903894
904895- En el evento OnSelectEditor ponemos el siguiente código - adáptalo si no necesitas editar la columna lógica 3, columna física 4:
905896
906897``` pascal
907898 if (Column.DesignIndex = 3) then
908899 begin
909900 GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
910901 GridCellMemo.Text:=Column.Field.AsString;
911902 Editor := GridCellMemo;
912903 end;
913904```
914905
915906- Supón que tu fuente de datos (datasource) se llame Datasource1 y DBGrid se llame ResultsGrid. Entonces, em eñ evemto OnEditingDone para GridCellMemo pon lo siguiente:
916907
917908(En el post original del foro se utilizaba el evento OnChange, que se lanzaría cada vez que el contenido se cambiase. Al utilizar ng OnEditingDone sólamente se lanza después que el usuario ha finalizado con la edición.)
918909
919910``` pascal
920911 if not(Datasource1.State in [dsEdit, dsInsert]) then
921912 Datasource1.Edit;
922913 Datasource1.DataSet.FieldByName(ResultsGrid.SelectedField.FieldName).AsString:=GridCellMemo.Text
923914```
924915
925916### Ejemplo: Como añadir un editor pulsador
926917
927918``` pascal
928919
929920// Muestra pulsador de forma condicional en la columna índice 1 o 2 si
930921// la celda en la columna de indice 1 está vacía
931922
932923procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer;
933924 var Editor: TWinControl);
934925begin
935926 if (aCol = 1) or (aCol = 2) then
936927 if StringGrid1.Cells[1,aRow] = '' then
937928 begin
938929 Editor := StringGrid1.EditorByStyle(cbsEllipsis);
939930 end;
940931 end;
941932
942933// Disparando(triggering) acción ...
943934procedure TForm1.StringGrid1EditButtonClick(Sender: TObject);
944935begin
945936 if StringGrid1.Col = 1 then Showmessage('Editor columna 1 clickeado');
946937 if StringGrid1.Col = 2 then Showmessage('Editor columna 2 clickeado');
947938end;
948939```
949940
950941### Ejemplo: Evitando mostrar campos de texto como "(Memo)" para DBGrids
951942
952943Cuando se utilizan sentencias SQL tales como "SELECT * FROM A" puede que veas "(Memo)" en lugar del contenido en la celda de la grid. Hay varias formas de solucionar esto, pero puede que la más fácil sea cambiar tu sentencia "SELECT * FROM A" por "SELECT CAST (Column as TEXT) AS Column FROM A". Esto es un problema común cuando se utiliza el componente DBGrid.
953944
954945``` pascal
955946 var
956947 sql : UTF8String;
957948 Queryu : TSQLQuery;
958949.....
959950 sql := 'SELECT cast(Name as TEXT) as Name FROM Client';
960951 Queryu.SQL.Text:=sql;
961952```
962953
963954### Ejemplo: Trabajando con Picklist, Como hacerlo de solo lectura y Como llenarlo en tiempo de ejecución
964955
965956Utilizando el envento OnSelectEditor de las grid podemos personalizar el comportamiento del editor PickList (ver estilo del pulsador cbsPickList). En el siguiente ejemplo el editor PickList de la columna 1 se modifica de tal forma que en en las filas impares el usuario puede introducir valores tecleándolos, en las pares los valores están limitados a los contenidos en su listado. Además, este ejemplo muestra como llenar el listado con diferentes valores dependiendo de la fila que se esté procesando.
966957
967958``` pascal
968959procedure TForm1.gridSelectEditor(Sender: TObject; aCol, aRow: Integer;
969960 var Editor: TWinControl);
970961begin
971962 if aCol=1 then begin
972963 if (Editor is TCustomComboBox) then
973964 with Editor as TCustomComboBox do begin
974965 if (aRow mod 2=0) then
975966 Style := csDropDown
976967 else
977968 Style := csDropDownList;
978969 case aRow of
979970 1:
980971 Items.CommaText := 'UNO,DOS,TRES,CUATRO';
981972 2:
982973 Items.CommaText := 'A,B,C,D,E';
983974 3:
984975 Items.CommaText := 'MX,ES,NL,UK';
985976 4:
986977 Items.CommaText := 'ROJO,VERDE.AZUL,AMARILLO';
987978 end;
988979 end;
989980 end;
990981end;
991982```
992983
993984### Alineando texto en StringGrids
994985
995986Este código muestra como utilizar diferentes alineamientos de texto en las columnas 2 y 3.
996987
997988``` pascal
998989
999990procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
1000991 aState: TGridDrawState);
1001992var
1002993 MyTextStyle: TTextStyle;
1003994begin
1004995 if (aCol=2) or (aCol=3) then
1005996 begin
1006997 MyTextStyle := StringGrid1.Canvas.TextStyle;
1007998 if aCol=2 then
1008999 MyTextStyle.Alignment := taRightJustify
10091000 else
10101001 if aCol=3 then
10111002 MyTextStyle.Alignment := taCenter;
10121003 StringGrid1.Canvas.TextStyle := MyTextStyle;
10131004 end;
10141005end;
10151006```
10161007
10171008### Multilineas en Grids, DBGrid
10181009
10191010Este ejemplo muestra como hacer texto multilinea en la celda \[3,2\]. Esto funciona de la misma manera que para DBGrid donde OnPrepareCanvas tiene parámetros para manejarse con TColumns y desde ahí con TFields.
10201011
10211012``` pascal
10221013
10231014procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
10241015 aState: TGridDrawState);
10251016var
10261017 MyTextStyle: TTextStyle;
10271018begin
10281019 if (aRow=2) or (aCol=3) then
10291020 begin
10301021 MyTextStyle := StringGrid1.Canvas.TextStyle;
10311022 MyTextStyle.SingleLine := false;
10321023 StringGrid1.Canvas.TextStyle := MyTextStyle;
10331024 end;
10341025end;
10351026```
10361027
10371028### Validating Entered Values
10381029
10391030Lazarus en su versión 0.9.29 introduce el evento OnValidateEntry para StringGrid, siendo un evento de tipo TValidateEntryEvent el cual tiene la siguente declaración:
10401031
10411032``` pascal
10421033
10431034TValidateEntryEvent =
10441035 procedure(sender: TObject; aCol, aRow: Integer; const OldValue: string; var NewValue: string) of object;
10451036```
10461037
10471038`aCol,aRow son las coordenadas de la celda que se va a validar.`\
10481039`OldValue es el valor que había en la celda cells[aCol,aRow] antes de comenzar la validación.`\
10491040`NewValue es el valor que será finalmente insertado en cells[aCol,aRow].`
10501041
10511042Debido a la forma en que trabaja StringGrid, que establece el valor de la celda mientras el usuario está editando (ver evento OnSetEditText de las grid y el método SetEditTex), lo que sucece es que cuando el evento OnValidateEntry se dispara, el valor de la celda que ya contiene el valor entrado (válido o no), puede evaluarse en base a sus argumentos OldValue y NewValue de forma que la validación puede cambiar el valor antiguo si es necesario.
10521043
10531044Usualment la valoración ocurre cuando el usuario mueve de la celda actual a otra. En caso de que la validación falle, es deseable que se mantenga el editor de celda tanto visible como enfocado en dicha celda de forma que el valor entrado pueda ser corregido por parte del usuario. Para hacer saber a la grid que la validación ha fallado es necesario que se lance una excepción. La grid en ese momento manejará la excepción con Application.HandleException y cancelará cualquier movimiento.
10541045
10551046Por ejemplo, supongamos que cell\[1,1\] deba albergar únicamente valores 'A' o 'B'. En este caso la validación podría realizarse con:
10561047
10571048``` pascal
10581049
10591050procedure TForm1.GridValidateEntry(sender: TObject; aCol,
10601051 aRow: Integer; const OldValue: string; var NewValue: String);
10611052begin
10621053 if (aCol=1) and (aRow=1) then begin
10631054 if grid.Cells[aCol,aRow]<>'A') and grid.Cells[aCol,aRow]<>'B') then begin
10641055 // Establecer un nuevo valor de forma que el usuario pueda presionar RETURN para continuar, por ejemplo.
10651056 NewValue := 'A';
10661057 // Otra opción es revertir el valor de la celda a su contenido previo (que se asume como válido).
10671058 // NewValue := OldValue;
10681059 // Utiliza EAbort en lugar de otro tipo de Excepción par evitar errores espureos.
10691060 // dialog boxes and backtraces
10701061 raise EAbort.Create('Solo se permite A o B aquí');
10711062 end else begin
10721063 // si no se lanza una excepción, se asume que el valor es válido, todavía si es necesario
10731064 // el valor final puede cambiarse todavía mediante el llenado con NewValue con un valor diferente.
10741065 // el ordenador lo sabe mejor :)
10751066 if grid.Cells[1,1]='A' then
10761067 NewValue := 'B'
10771068 else
10781069 NewValue := 'A';
10791070 end;
10801071 end;
10811072end;
10821073```
10831074
10841075### Ordenando columnas (Columns) o filas (Rows)
10851076
10861077La propiedad ColumnClickSorts permite el ordenado automático de la grid cuando el usuario hace click en la cabecera de la columna. Realizando el click varias veces conmuta el tipo de ordenación. Se muestran imágenes de ordenación de columna predeterminadas para indicar que columna ha sido clickeada.
10871078
10881079Mediante código se puede utilizar el método SortColRow(). Su primer parámetro es un valor booleano que indica true si lo que se va a ordenar es una columna o false si se trata de una fila. El segundo parámetro es el índice de fila o columna. Los siguientes parámetros son opcionales y seleccionan subrangos de filas (para ordenamiento de columnas) o columnas (para ordenamiento de filas) a ser ordenados. Si no se ha especificado el último parámetro entonces se ordena bien sea la fila o la columna completa. El sistema de ordenación utiliza un algoritmo de ordenado rápido (quicksort) que se puede cambiar en el caso de overrides el método sort() y las llamadas de comparación doCompareCells.
10891080
10901081Por defecto esto orcena el contenido de las celdas tanto en orden ascendente como descencente el cual es seleccionable mediante la propiedad SortOrder. Por defecto utiliza el orden ascendente.
10911082
10921083``` pascal
10931084// sort column 3 in ascending order
10941085grid.SortColRow(true, 3);
10951086
10961087// sort column 3 in descending order, skip fixed rows a top
10971088grid.SortOrder := soDescending; // or soAscending
10981089grid.SortColRow(true, 3, grid.FixedRows, grid.RowCount-1);
10991090```
11001091
11011092Para la ordenación personalizada de número, fechas, estados, etc. SgringGrid tiene el evento OnCompareCells que los usuarios pueden utilizar de la manera del siguiente ejemplo:
11021093
11031094``` pascal
11041095
11051096procedure TForm1.GridCompareCells(Sender: TObject; ACol, ARow, BCol, BRow: Integer; var Result: integer);
11061097begin
11071098 // El resultado será uno cualquiera de estos <0, =0, or >0 para un orden normal.
11081099 result := StrToIntDef(Grid.Cells[ACol,ARow],0)-StrToIntDef(Grid.Cells[BCol,BRow],0);
11091100 // Para el orden inverso simplemente niega el resultado (eg. basado en SortOrder de las grids).
11101101 if StringGrid1.SortOrder = soDescending then
11111102 result := -result;
11121103end;
11131104```
11141105
11151106Puedes utilizar también OnCompareCells cuando está habilitado el reordenado automático de columna a través de la propiedad ColumnClickSorts.
11161107
11171108### Ordenando columnas o filas en una DBGrid con flejas de ordenación en la cabecera de la columna
11181109
11191110Aquí tenemos un ejemplo que ordenará una **DBgrid** utilizando el evento **OnTitleClick** y una **TSQLQuery** e índices. Esto debería funcionar también para cualquier otro conjunto de datos (data set) como puede ser el caso de **TbufDataset**.
11201111
11211112La función utiliza la propiedad **column.tag** para almacenar el estado de cada columna en la que realizamos el click, de tal manera que cuando se vuelve a alguna de ellas que previamente ha sido ya ordenada esto aplicará la flecha visual de ordenación correcta. **Prerequisitos:**
11221113
11231114- Se necesita un listado de imágenes para almaenar las flechas de ordenación arriba/abajo. Para ello asignamos el listado de imágenes (imagelist) a la propiedad **TitleImageList** de la grid.
11241115
11251116- Hay que asegurarse de que **[[doc:fcl/sqldb/tsqlquery.maxindexescount.html|TSQLQuery.MaxIndexesCount]]** es suficientemente largo para contener los nuevos índices que estamos creando. Para este ejemplo lo hemos establecido a 100.
11261117- Además vamos a necesitar una variable privada para almacenar la última columna que hemos utilizado para ordenar, para este ejemplo se ha llamado **FLastColumn**.
11271118
11281119Para este ejemplo la **TSQLQuery** que utilizamos la llamamos OpenQuery.
11291120
11301121En vistas a reutilizar el componente **TSQLQuery** con otra sentencia SQL, se deben limpiar los índices después de realizar la ordenación ya que en caso contrario se lanzará una excepción al abrir **TSQLQuery** con una sentencia de SQL diferente.
11311122
11321123``` pascal
11331124FLastColumn: TColumn; // Almacena la última columna que hemos ordenado en la grid.
11341125
11351126procedure TSQLForm.ResultsGridTitleClick(Column: TColumn);
11361127const
11371128 ImageArrowUp=0; // Debe coincidir con la imagen de flecha arriba contenida en imagelist.
11381129 ImageArrowDown=1; // Debe coincidir con la imagen de flecha abajo conenida en imagelist.
11391130var
11401131 ASC_IndexName, DESC_IndexName:string;
11411132 procedure UpdateIndexes;
11421133 begin
11431134 // Ensure index defs are up to date
11441135 OpenQuery.IndexDefs.Updated:=false; {<<<-- ¡¡¡Esta línea es crítica!!!. IndexDefs.Update no actualizará
11451136 // si la encuentra a true, lo cual sucederá en el primer ordenamiento de columna.}
11461137 Openquery.IndexDefs.Update;
11471138 end;
11481139begin
11491140 ASC_IndexName:='ASC_'+Column.FieldName;
11501141 DESC_IndexName:='DESC_'+Column.FieldName;
11511142 // Los índices no pueden ordenar tipos binarios tales como ftMemo, ftBLOB
11521143 if (Column.Field.DataType in [ftBLOB,ftMemo,ftWideMemo]) then
11531144 exit;
11541145 // Chequea si ya existe un índice ascendente para esta columna,
11551146 // en caso contrario, crear uno.
11561147 if OpenQuery.IndexDefs.IndexOf(ASC_IndexName) = -1 then
11571148 begin
11581149 OpenQuery.AddIndex(ASC_IndexName,column.FieldName,[]);
11591150 UpdateIndexes; // Asegurarse de que index defs está actualizado.
11601151 end;
11611152 // Comprobar si ya existe un índice descendente para esta columna,
11621153 // caso contrario crear uno.
11631154 if OpenQuery.IndexDefs.IndexOf(DESC_IndexName) = -1 then
11641155 begin
11651156 OpenQuery.AddIndex(DESC_IndexName,column.FieldName,[ixDescending]);
11661157 UpdateIndexes; // Asegurarse de que index defs están actualizados.
11671158 end;
11681159
11691160 // Use the column tag to toggle ASC/DESC
11701161 column.tag := not column.tag;
11711162 if boolean(column.tag) then
11721163 begin
11731164 Column.Title.ImageIndex:=ImageArrowUp;
11741165 Openquery.IndexName:=ASC_IndexName;
11751166 end
11761167 else
11771168 begin
11781169 Column.Title.ImageIndex:=ImageArrowDown;
11791170 OpenQuery.IndexName:=DESC_IndexName;
11801171 end;
11811172 // Remover la flecha de ordenamiento de la columna que previamente hemos ordenado.
11821173 if (FLastColumn <> nil) and (FlastColumn <> Column) then
11831174 FLastColumn.Title.ImageIndex:=-1;
11841175 FLastColumn:=column;
11851176end;
11861177```
11871178
11881179### Seleccionando Registros en una DBGrid utilizando checkboxes
11891180
11901181El objetivo es ser capaz de seleccionar arbitrariamente registros en una dbgrid utilizando checkboxes, la grid tiene la habilidad de mostrar checkboxes automáticamente cuando detecta que hay valores booleanos, para otros tipos d campos el usuario puede escoger manualmente el cbsCheckboxColumn ButtonStyle para la columna. Para este tipo de columnas el usuario directamente hace click en el correspondiente checkbox y como resultado el contenido del campo se modifica apropiadamente.
11911182
11921183Pero, ¿que sucede si tal campo no está disponible en nuestro DataSet? ¿o si no necesitamos que la grid entre en modo edición cuando chequeamos el checkbox?. Añadiendo una columna fieldless con ButtonStyle=cbsCheckboxColumn mostrará todos los checkboxes en gris claro y deshabilitados porque no hay un campo enlazado a esta columna y por tanto nada que modificar. Como necesitamos manejar el estado del checkbox por nosotros mismos, necesitamos almacenar el estado en algún lugar para cada registro. Para ello podemos utilizar la clase TBookmarklist (definida en la unidad dbgrids.pas) donde la propiedad CurrentRowSelected puede decirnos si el registro actual está seleccionado o no. Mediante el uso de eventos OnCellClick y OnUserCheckboxState de la grid podemos hacer el seguimiento del estado del checkbox.
11931184
11941185Ten en cuenta que esta técnica necesita de Lazarus r31148 o posterior que implementan el event OnUserCheckboxState.
11951186
11961187``` pascal
11971188...
11981189uses ..., dbgrids, stdctrls, ...
11991190
12001191type
12011192
12021193 { TForm1 }
12031194
12041195 TForm1 = class(TForm)
12051196 ...
12061197 procedure DBGrid1CellClick(Column: TColumn);
12071198 procedure DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
12081199 procedure FormCreate(Sender: TObject);
12091200 procedure FormDestroy(Sender: TObject);
12101201 ...
12111202 private
12121203 RecList: TBookmarklist;
12131204 ...
12141205 end;
12151206
12161207procedure TForm1.DBGrid1CellClick(Column: TColumn);
12171208begin
12181209 if Column.Index=1 then
12191210 RecList.CurrentRowSelected := not RecList.CurrentRowSelected;
12201211end;
12211212
12221213procedure TForm1.FormCreate(Sender: TObject);
12231214begin
12241215 RecList := TBookmarkList.Create(DbGrid1);
12251216end;
12261217
12271218procedure TForm1.FormDestroy(Sender: TObject);
12281219begin
12291220 RecList.Free;
12301221end;
12311222
12321223procedure TForm1.DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
12331224begin
12341225 if RecList.CurrentRowSelected then
12351226 AState := cbChecked
12361227 else
12371228 AState := cbUnchecked;
12381229end;
12391230```
12401231
12411232### Resaltando (Highlighting) la fila y columna de la celda seleccionada
12421233
12431234En Lazarus revision 40276 se ha añadido una opción **gdRowHighlight** , que funciona de manera similar a **goRowSelect** pero que utiliza un color más resaltado para el rectángulo de selección y enfoque solamente para para la celda seleccionada y no para toda la fila. Esto funciona bien para filas pero ¿y para las columnas?.
12441235
12451236Esta sección presenta un modo de resaltar columnas, filas o ambas (podemos encontrar un ejemplo que resalta solo columnas y filas en lazarus/examples/gridexamples/spreadshee, donde este **COMO HACER** es realmente una extensión de este ejemplo). Utiliza dos eventos de la grid **OnBeforeSelection** y **OnPrepareCanvas**, el dibujado no es necesario.
12461237
12471238El evento **OnBeforeSelection** es disparado cuando when the selection is about the change, en este evento podemos saber que celda está actualmente seleccionada y cual será seleccionada a continuación. Utilizamos esta información para invalidar la fila o columna completas de ambas celdas tanto la antigua como la nueva. Cuando comienza el siguiente ciclo de pintado, la grid será instruida para pintar las celdas que pertenezcan a las áreas invalidadas. Uno de los primeros pasos para pintar se llama evento **OnPrepareCanvas** (si existe) para configurar las propiedades por defecto de canvas. Nosotros utilizaremos este evento para establecer la fila o columna resaltada:
12481239
12491240``` pascal
12501241
12511242procedure TForm1.gridBeforeSelection(Sender: TObject; aCol, aRow: Integer);
12521243begin
12531244 // Podemos decidir aquí si necesitamos resaltar columnas, filas o ambas.
12541245 // En este ejemplo resaltamos ambas.
12551246 if Grid.Col<>aCol then
12561247 begin
12571248 // a change on current column is detected
12581249 grid.InvalidateCol(aCol); // Invalida la nueva columna de celda seleccionada.
12591250 grid.InvalidateCol(grid.Col); // Invalida la actual columna seleccionada (esta sera la 'old')
12601251 end;
12611252 if Grid.Row<>aRow then
12621253 begin
12631254 grid.InvalidateRow(aRow); // Invalida la nueva fila de celda seleccioanda.
12641255 grid.InvalidateRow(grid.Row); // Invalida la fila actual seleccionada (esta será la 'old').
12651256 end;
12661257end;
12671258
12681259procedure TForm1.gridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
12691260 aState: TGridDrawState);
12701261begin
12711262 if gdFixed in aState then
12721263 begin
12731264 if (aCol=grid.Col) or (aRow=grid.Row) then
12741265 grid.Canvas.Brush.Color := clInactiveCaption; //Esto resaltará además las cabeceras de fila o columna.
12751266 end else
12761267 if gdFocused in aState then begin
12771268 // Dejamos solas la celda actual seleccionada/enfocada.
12781269 end else
12791270 if (aCol=Grid.Col) or (aRow=grid.Row) then
12801271 grid.Canvas.Brush.Color := clSkyBlue; // Resaltamos filas y columnas con el color clSkyBlue.
12811272end;
12821273```
12831274
12841275\<\<\>\>
12851276
12861277#### property OnBeforeSelection: TOnSelectEvent
12871278
12881279#### property OnCompareCells: TOnCompareCells
12891280
12901281#### property OnPrepareCanvas: TOnPrepareCanvasEvent
12911282
12921283#### property OnDrawCell: TOnDrawCell
12931284
12941285#### property OnEditButtonClick: TNotifyEvent
12951286
12961287#### property OnSelection: TOnSelectEvent
12971288
12981289#### property OnSelectEditor: TSelectEditorEvent
12991290
13001291#### property OnTopLeftChanged: TNotifyEvent
13011292
13021293#### procedure AutoAdjustColumns;
13031294
13041295#### procedure BeginUpdate;
13051296
13061297#### procedure Clear;
13071298
13081299#### procedure DeleteColRow(IsColumn: Boolean; index: Integer);
13091300
13101301#### function EditorByStyle(Style: TColumnButtonStyle): TWinControl;
13111302
13121303#### procedure EndUpdate(UO: TUpdateOption); overload;
13131304
13141305#### procedure EndUpdate(FullUpdate: Boolean); overload;
13151306
13161307#### procedure EndUpdate; overload;
13171308
13181309#### procedure ExchangeColRow(IsColumn: Boolean; index, WithIndex:Integer);
13191310
13201311#### function IscellSelected(aCol,aRow: Integer): Boolean;
13211312
13221313#### function IscellVisible(aCol, aRow: Integer): Boolean;
13231314
13241315#### procedure LoadFromFile(FileName: string);
13251316
13261317#### function MouseToCell(Mouse: TPoint): TPoint; overload;
13271318
13281319#### function MouseToLogcell(Mouse: TPoint): TPoint;
13291320
13301321#### function MouseToGridZone(X,Y: Integer): TGridZone;
13311322
13321323#### function CellToGridZone(aCol,aRow: Integer): TGridZone;
13331324
13341325#### procedure MoveColRow(IsColumn: Boolean; FromIndex, ToIndex: Integer);
13351326
13361327#### procedure SaveToFile(FileName: string);
13371328
13381329#### procedure SortColRow(IsColumn: Boolean; index:Integer); overload;
13391330
13401331#### procedure SortColRow(IsColumn: Boolean; index,FromIndex,ToIndex: Integer); overload;
13411332
13421333#### property AllowOutboundEvents:boolean;
13431334
13441335Protegida en TCustomGrid, pública (public) en TCustomDrawGrid y descendientes. Normalmente cuando un usuario hace click en un punto sobre un espacio vacío después de las celdas (por ejemplo si la grid tiene tres filas pero el usuario hace click en una imaginaria cuarta fila) la celda seleccionada (que obtiene el foco:focused) se moverá a la celda más cercana al punto que se ha realizado el click, podemos llamar a esto un evento externo y su valor por defecto es verdadero (true) y es un comportamiento que se añadió desde un principio. Esta propiedad fue añadida para simular el comportamiento que tienen en Delphi donde los eventos fuera de su área de acción no estan disponibles. Si se desea mantener la compatibilidad con Delphi se debe establecer esta propiedad al valor falso (false).
13451336
13461337### Lo siguiente es ya antiguo pero se deja como referencia, puede que ya esté implementado y/o solucionado
13471338
13481339## Todo
13491340
13501341- TInplaceEditor Support
13511342
13521343### known problems
13531344
1354134529-marzo-2005:
13551346
13561347- mouse autoedit
13571348 - linux: clicking a selected cell doesn't trigger the autoedit function (the editor lost focus inmmediatelly)
13581349 - windows: it should work only if the grid has the focus, if not it should focus and a second click should autoedit (cannot detect if the grid was previously focused or not)
13591350- ColumnWidths: linux, windows: dbgrid start with default column widths instead of calculated ones (it's disabled because early canvas creation causes strange effects if the parent is a notebook page)
13601351- Resizing Columns: linux, windows. Resizing a column should not hide the editor (specially in dbgrid if we are inserting)
13611352- MouseWheel:
13621353 - linux: mousewheel doesn't work as it should, (seems it's calling the default mousewheel handler)
13631354 - windows: patch was sent.
13641355- Double painting: Apparently dbgrid paints the cell background twice (once with fillrect and once in textRect)
13651356- AutoFillColumns: sometimes the clientwidth is evaluated incorrectly (sometimes there are phantom scrollbars)
13661357
13671358---
13681359*From the Free Pascal wiki, page "Grids Reference Page/es", as it stood on 2022-01-21 after a change by Slawek, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
13691360Category: Castellano
13701361Category: Español

Back to history