Sign in Join
Wiki copytemporary

Grids Reference Page/es

Deutsch · English · Español · Polski · Русский

Objetivo

Este 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:

  1. Servir de introducción a los componentes tipo grid para la gente que tenga pocos o ningún conocimiento previo de Lazarus.
  2. Documentar las diferencias con respecto a los componentes tipo grid utilizados en Delphi.
  3. Documentar la nueva funcionalidad de las grids en Lazarus.
  4. Crear referencias y ejemplos para estos componentes.

Descripción

Una 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).

El 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.

Dada 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í

El siguiente diagrama muestra el árbol de herencia con los tipos existentes:

Árbol de herencia

Un ejemplo inicial

Como 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.

  1. Crear una nueva aplicación.
    • Desde el menú principal seleccionar: Archivo -> Nuevo ... -> Proyecto -> Aplicación.
    • Una vez selecciona Aplicación pulsamos Aceptar.
    • Nos muestra un formulario (Form1) vacío nuevo.
  2. Situar una grid (rejilla) sobre el formulario Form1.
    • Desde la paleta de componentes seleccionar la solapa "Additional".
    • Hacer Click sobre el icono TStringGrid .
    • Hacer Click sobre el formulario, cerca de la esquina superior izquierda. De esta forma aparece una grid nueva vacía.
  3. Situar un pulsador (button) en el formulario.
    • Desde la paleta de componentes seleccionar la solapa "Standard".
    • Hacer Click sobre el icono TButton .
    • Hacer Click sobre un área vacía del formulario. De esta forma aparece un nuevo pulsador(button).
  4. 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.
    • pascal
      StringGrid1.Cells[1,1] := '¡ Hola Mundo !';
    • Ejecuta el programa haciendo Click en el icono de play .
  5. 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.

Diferencias entre las grids de Delphi y las de Lazarus

Los 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,

Posteriormente, 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.

Tambié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.

Diferencias

Las siguientes son diferencias evidentes:

Nuevas funcionalidades

Ajustes para afianzar la compatibilidad con grids de Delphi

Aquí 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.

Referencia de Grids

Información

El punto de partida para referencias sobre TCustomGrid, TDrawGrid, TCustomDrawGrid, TCustomStringGrid y TStringGrid es: Referencia unit Grids.pas

Para TCustomDBGrid y TDBgrid es: Referencia: unit DBGrids.pas

Hasta 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.

En 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.

Cosas por hacer: el resto de esta sección desaparecerá. Su contenido se trasladará a Referencia: unit Grids.pas

TCustomGrid

Ver la Referencia: TCustomGrid

Propiedad AllowOutboundEvents

Protegida 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.

Propiedad Columns

Lazarus 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.

Las 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.

Por ejemplo, si a una grid base con with ColCount := 5 y FixedCols := 2 hacemos:

Por tanto podemos concluir que:

En 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.

En tiempo de ejecución, las columnas se pueden modificar con un código como este:

pascal
  var
    c: TGridColumn;
  begin
    // Añade una columna personalizada a la grid
    c := Grid.Columns.Add;
    // La modifica
    c.title.caption := 'Precio';       // Establece el valor caption de la columna.
    c.align := taRightJustify;         // Alinea a la derecha el contenido de la columna.
    c.color := clMoneyGreen;           // Cambia el valor por defecto a clMoneyGreen.
    c.Index := 0;                      // La hace la primera columna.
    // Accede a una columna existente
    grid.columns[0].Width := 60;       // Cambia el ancho de la columna 0 a 60 pixeles.
    // Elimina una columna existente.
    grid.columns.delete(0);            // Elimina la columna 0
    ....
  end;

Adicionalmente, 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.

Actualmente no hay planes para hacer que las grids utilicen únicamente columnas personalizadas.

TCustomDBGrid

TCustomDBGrid es la base para TDBGrid.

No exponen las propiedades Col y Row. Para ir a una determinada columna, utilizar e.g. la propiedad SelectedIndex.

Un método público interesante es AutoSizeColumns.

Procedimiento AutoSizeColumns

Este 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.

Procedimiento InplaceEditor

Ver 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?

pascal
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: char);
var
  S: String;
begin
  if (Key in [',','.']) then
  begin
    //Al contrario que Delphi no todos los InPlaceEditors son editores para el tipo string, por tanto verificalo.
    if (DBGrid1.InplaceEditor is TStringCellEditor) then
    begin
      S := TStringCellEditor(DBGrid1.InplaceEditor).EditText;
      if Pos(',',S) > 0 then
        Key := #0
      else
        Key := ',';
    end;
  end;
end;

TCustomStringGrid

TCustomStringGrid 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.

Las siguientes propiedades o métodos son públicos y están además disponibles para TStringGrid.

Ver la referencia completa para TCustomStringGrid

Procedimiento AutoSizeColumn(aCol: Integer);

Este 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.

Procedimiento AutoSizeColumns;

Redimensiona 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.

Procedimiento Clean; overload;

Limpia todas las celdas de la grid, fijas o no fijas.

Procedimiento Clean(CleanOptions: TGridZoneSet); overload;

Limpia todas las celdas en la grid sujetas a lo establecido en CleanOptions. Ver TGridZoneSet para mas información. Algunos ejemplos:

Procedimiento Clean(StartCol,StartRow,EndCol,EndRow: integer; CleanOptions:TGridZoneSet); overload;

Hace 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:

Procedimiento Clean(aRect: TRect; CleanOptions: TGridZoneSet); overload;

Lo 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,[]);

==== Procedimiento SaveToCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true); ====

Salva el contenido de la grid a un fichero con formato CSV (Comma Separated Values) conteniendo valores separados por comas (añadido en Lazarus r32179).

El 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.

Se 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.

Para 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.

Hay 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.

Si 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.

Los datos normales de salida CSV deben comenzar en la primera fila no fija de la grid.

==== Procedimiento LoadFromCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true); ====

Carga el contenido de la grid desde un fichero con formato de valores separados por coma (CSV) (añadido en Lazarus r32179).

Las 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.

El argumentoAfilename especifica el nombre de fichero con el contenido CSV.

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 ;

El 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.

Si 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.

Propiedad Cols[index: Integer]: TStrings lee GetCols escribe SetCols;

Obtiene/establece un listado de cadenas desde/hacia la columna índice de la grid comenzando desde la fila índice 0 hasta RowCount-1.

Ejemplos
pascal
Grid.Cols[2] := ListBox1.Items;
pascal
procedure TForm1.FillListBox1;
var 
  StrTempList: TStringList;
begin
  StrTempList := TStringList(Grid.Cols[4]);
  if StrTempList<>nil then 
  begin
    ListBox1.Items.Assign(StrTempList);
    StrTempList.Free;
  end;
end;
Notas.

Esta propiedad funciona de una manera diferente en Lazarus respecto a Delphi cuando obtiene datos desde la grid.

En 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.

Esto significa además que los cambios en el listado retornado no afectaran al contenido de la grid o su disposición.

Ver el ejemplo "obtener".

property Rows[index: Integer]: TStrings read GetRows write SetRows;

Get/set a list of strings from/to the given grid's row index starting from column index 0 to column ColCount-1.

Notas.

Esta 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.

Ejemplos
pascal
Grid.Rows[2] := ListBox1.Items;
pascal

procedure TForm1.FillListBox1;
var 
  StrTempList: TStringList;
begin
  StrTempList := TStringList(Grid.Rows[4]);
  if StrTempList<>nil then 
  begin
    ListBox1.Items.Assign(StrTempList);
    StrTempList.Free;
  end;
end;
pascal

// Esto no funcionará y causará una pérdida de memoria.
// porque el StringList retornado no se libera.

Grid.Rows[1].CommaText := '1,2,3,4,5';
Grid.Rows[2].Text := 'a'+#13#10+'s'+#13#10+'d'+#13#10+'f'+#13#10+'g'; 
 
// Solucionando el primer caso.

Lst:=TStringList.Create;  // Crea el StringList asignándole recursos de memoria.
Lst.CommaText := '1,2,3,4,5';
Grid.Rows[1] := Lst;
Lst.Free; // Libera los recursos de memoria asignados a StringList cuando ya no lo necesitamos.

Propiedad UseXORFeatures;

Propiedad booleana, su valor por defecto es False;

Esta 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].

Esto 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.

TValueListEditor

TValueListEditor es un control derivado de TCustomStringGrid para edición de pares Key-Value.

Propiedad DisplayOptions

Controla varios aspectos de la aparariencia de TValueListEditor.

Propiedad TitleCaptions

Establece los valores de los captions del título (si doColumnTitles se encuentra en DisplayOptions).

Si DisplayOptions carece de vakir doColumnTitles entonces se utilizan los captions por defecto.

Propiedad Strings

Provee acceso al listado de cadenas que alberga los pares Key-Value.
Los pares Key-Value deben encontrarse en el formulario:
'KeyName=Value'

Propiedad ItemProps

Se puede utilizar esta propiedad para controlar como se pueden editar los elementos en las columnas "Value"
Se controla mediante el establecimiento de las propiedades ItemProp's EditStyle y ReadOnly.

Propiedad KeyOptions

KeyOptions es un conjunto de TKeyOptions que controla de que manera el usuario puede modificar los contenidos de la columna "Key".

Propiedad DropDownRows

Si 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.

Función DeleteRow

Borra el par Key-Value de la fila indexada eliminando la fila completamente.

Función InsertRow

Inserta una fila en la grid y establece el par Key-Value. Retorna el índice de la nueva fila insertada.

Función IsEmptyRow

Retorna true si las celdas de la fila insertada están vacias (Keys[aRow]=; Values[aRow]=).

Function FindRow

Retutns the row that has the specified key name.

Funcion RestoreCurrentRow

Deshace la edición en la fila actual (si el editor está todavía enfocado). Sucede cuando el usuario presiona la tecla Escape (Esc).

Comportamiento alterada de algunas propiedades derivado de TCustomStringGrid

Opciones de propiedades

Debido a la naturaleza de TValueListEditor sus opciones tienen ciertas restricciones:

Propiedad FixedRows

Puede valer solamente 1 (mostrar los títulos de las columnas) o 0 (no mostrar los títulos de las columnas).

Propiedad ColCount

Es siempre 2.

Comentarios generales sobre el uso de TValueListEditor

Cuando se manipulan los contenidos de ValueListEditor (la grid), es recomendable manipular las propiedades Strings subyacentes.
Si 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().
Si se intenta utiliza métodos ancestros para manipular las filas o las columnas (e.g. Columns.Add) puede desencadenar un crash.

Trabajando con grids

Personalizando grids

Las grid (rejillas) son componentes derivados de la clase TCustomControl, 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).

Propiedades y Eventos para personalizar las grids

Algunas 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:

Otras propiedades que también afectan al aspecto de las grids.

Options.

La 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.

(escribir más contenido)

Descripción del proceso de dibujado de las grids

Al 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.

El proceso es el siguiente:

Diferencias con Delphi

Selección de celdas de la grid

La 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.

La 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.

Una 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.

After 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).

Ten 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.

Diferencias con Delphi

Cuando las propiedades de base que trae la grid no son suficiente: grids derivadas

Las grids derivadas usualmente tienen que sobreescribir los siguientes métodos:
DrawAllRows: Dibuja todas las filas visibles.
DrawRow: Dibuja todas las celdas en una fila.
DrawRow 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.
DrawCell:
DrawCellGrid:
DrawCellText:
DrawFocusRect:
(ampliar esta sección con más contenido).\

¿Qué sucede en el método TCustomGrid.Paint

El 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.

Métodos adicionales para dibujar en TCustomGrid

Estos 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.

Métodos de dibujado introducidos por TCustomDrawGrid

Como 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:

Salvar y recuperar el contenido de una Grid

Los 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).

El 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:

soDesign: Salvar & cargar ColCount, RowCount, FixedCols, FixedRows, ColWidths, RowHeights y Options (TCustomGrid)

soPosition: Salvar & cargar Posición de Scroll, Row, Col y Selection (TCustomGrid)

soAttributes: Salvar & cargar Colores, Alineación de texto & Disposición, etc. (TCustomDrawGrid)

soContent: Salvar & cargar texto (TCustomStringGrid)

soAll: Establecer todas las opciones (soAll:=[soDesign,soPosition,soAttributes,soContent];)

No 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.

La opción soAttributes no se utiliza en las grids standar de Lazarus, se encuentra ahí para las grids derivadas.

Cuando 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..

Nota:

Una 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:


Ejemplo:

  1. Primeramente, ve al menú en "Fichero -> Nuevo -> Proyecto -> Application";
  2. Emplaza un componente TStringGrid en el formulario (lo veremos como StringGrid1);
  3. Emplaza un componente TButton y otro TOpenDialog en el forumulario (los veremos como Button1 y OpenDialog1);
  4. Añade un evento OnCreate al formulario (tendrá lugar cuando se cree el forumulario).
  5. añade un evento OnClick para el button (tendrá lugar cuando cuando hagamos click sobre el pulsador Button1).
pascal
unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Grids,
  Buttons, StdCtrls, XMLCfg;

type

  { TForm1 }
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure Form1Create(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end; 

var
  Form1: TForm1; 

implementation

{ TForm1 }

procedure TForm1.Form1Create(Sender: TObject);
begin
 //sets the SaveOptions at creation time of the form 
 stringgrid1.SaveOptions := [soDesign,soPosition,soAttributes,soContent];
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 //Pregunta si se ha lanzado el método Execute  de OpenDialog1 
 //de forma que cuando esto suceda, el usuario selecciona el fichero .XML a cargar
 //cuyo nombre ha sido almacenado en la propiedad FileName.

 if opendialog1.Execute then
 Begin
   //Limpia la grid.
   StringGrid1.Clear;
   //Carga desde el fichero .XML seleccionado la grid.
   StringGrid1.LoadFromFile(OpenDialog1.FileName);
   //Refresca la Grid.
   StringGrid1.Refresh;
 End;
end;

initialization
  {$I unit1.lrs}

end.

Fichero de ejemplo .XML: (Copia el texto de debajo dentro de un fichero tipo texto (txt). No olvides poner la cabecera definitoria xml :-))

xml
<?xml version="1.0"?>
<CONFIG>
  <grid version="3">
    <saveoptions create="True" position="True" content="True"/>
    <design columncount="2" rowcount="5" fixedcols="1" fixedrows="1" defaultcolwidth="64" defaultRowHeight="20">
      <options>
        <goFixedVertLine value="True"/>
        <goFixedHorzLine value="True"/>
        <goVertLine value="True"/>
        <goHorzLine value="True"/>
        <goRangeSelect value="True"/>
        <goDrawFocusSelected value="False"/>
        <goRowSizing value="False"/>
        <goColSizing value="False"/>
        <goRowMoving value="False"/>
        <goColMoving value="False"/>
        <goEditing value="False"/>
        <goTabs value="False"/>
        <goRowSelect value="False"/>
        <goAlwaysShowEditor value="False"/>
        <goThumbTracking value="False"/>
        <goColSpanning value="False"/>
        <goRelaxedRowSelect value="False"/>
        <goDblClickAutoSize value="False"/>
        <goSmoothScroll value="True"/>
      </options>
    </design>
    <position topleftcol="1" topleftrow="1" col="1" row="1">
      <selection left="1" top="1" right="1" bottom="1"/>
    </position>
    <content>
      <cells cellcount="10">
        <cell1 column="0" row="0" text="Title Col1"/>
        <cell2 column="0" row="1" text="value(1.1)"/>
        <cell3 column="0" row="2" text="value(2.1)"/>
        <cell4 column="0" row="3" text="value(3.1)"/>
        <cell5 column="0" row="4" text="value(4.1)"/>
        <cell6 column="1" row="0" text="Title Col2"/>
        <cell7 column="1" row="1" text="value(1.2)"/>
        <cell8 column="1" row="2" text="value(2.2)"/>
        <cell9 column="1" row="3" text="value(3.2)"/>
        <cell10 column="1" row="4" text="value(4.2)"/>
      </cells>
    </content>
  </grid>
</CONFIG>

--Raditz 21:06, 11 Jan 2006 (CET) desde ARGENTINA

Editores de celdas de la grid.

La grid utiliza editores de celda para cambiar el contenido de las celdas.

Para 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:

  1. 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.
  2. So eñ texto en la celdai representa una fecha, presentar un calendario emergente de forma que se pueda especificar una fecha facilmente.

Algunas 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.

Este 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.

Editores de celdas integrados

La 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.

Los editores de celdas integrados son Button, Edit, y Picklist.

Utilizando editores de celdas

Los usuarios pueden especificar que editor se utilizará para las celdas escogiendo uno de estos dos métodos.

  1. 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.
  2. 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).

Establecer 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:

pascal

if ColCB < StringGrid1.Columns.Count then StringGrid1.Columns.Items[ColCB].ButtonStyle:=cbsCheckboxColumn;

Descripción de estilos del editor

La 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.

Este 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.

Este 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.

OnEditButtonClick 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.

Una 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

Este estilo de editor se implementa utilizando TButtonCellEditor, un descendiente directo de TButton.

Este 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.

Utilizado 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.

  1. 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]
  2. En OnSelectEditor, los programadores obtienen la instancia TPickListCellEditor utilizando la función EditorByStyle(cbsPickList). Ver aquí para un ejemplo

var Lst:TPickListCellEditor; 
  begin [...] 
    Lst:=TPickListCellEditor(EditorByStyle(cbsPickList)); 
    Lst.clear; 
    Lst.Items.add('One'); 
    Lst.items.add('Two'); 
    Editor:=Lst; 
  end;

El 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.

Puede 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).

Obtener o establecer el valor de un checkbox en una celda se realiza de la siguiente manera:

pascal
StringGrid1.Cell[x,y]:='Z';

donde 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).

Si 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.

Los valores que se utilizan para reconocer los estados de marcado o desmarcado se establecen en las propiedades de columnas ValueChecked y ValueUnchecked.

En 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.

Este 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.

Este 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.

El 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.

El caption de button es la string correspondiente de la celda.

Editando Grids

El 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 eventoOnSetEditText 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.

Opciones y propiedades que afectan a la edición

El 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.
Al inicio el estado editable de las grids es diferente, para TDrawGrid y TStringGrid la edición está deshabilitada, para TDbGrid está habilitada por defecto.
La propiedad Options tiene varios elementos que lidian con la edición, y se encuentran descritas a continuación:\

Como hacer y Ejemplos

Enfocando una celda

Enfocar 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:

pascal
StringGrid1.row := 9;
StringGrid1.col := 8;

Ejemplo: Como establecer un editor de celdas personalizado

Ver lazarus/examples/gridexamples/gridcelleditor/gridcelleditor.lpi (desde Lazarus 1.2)

Ejemplo: Como establecer un editor memo para dbgrids

Tu puedes, por supuesto, utilizar otro control en lugar de un TMemo. Ajustalo a tu gusto. Adaptado desde 1 (simplificado para utilizar la propiedad SelectedFieldRect , ver 2)

pascal
  if (Column.DesignIndex = 3) then
  begin
      GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
      GridCellMemo.Text:=Column.Field.AsString;
      Editor := GridCellMemo;
  end;

(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.)

pascal
  if not(Datasource1.State in [dsEdit, dsInsert]) then
    Datasource1.Edit; 
  Datasource1.DataSet.FieldByName(ResultsGrid.SelectedField.FieldName).AsString:=GridCellMemo.Text

Ejemplo: Como añadir un editor pulsador

pascal

// Muestra pulsador de forma condicional en la columna índice 1 o 2 si 
// la celda en la columna de indice 1 está vacía

procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer; 
  var Editor: TWinControl);
begin
  if (aCol = 1) or (aCol = 2) then
    if StringGrid1.Cells[1,aRow] = '' then
    begin
      Editor := StringGrid1.EditorByStyle(cbsEllipsis);
    end;
  end;

// Disparando(triggering) acción ...
procedure TForm1.StringGrid1EditButtonClick(Sender: TObject);
begin
  if StringGrid1.Col = 1 then Showmessage('Editor columna 1 clickeado');
  if StringGrid1.Col = 2 then Showmessage('Editor columna 2 clickeado');
end;

Ejemplo: Evitando mostrar campos de texto como "(Memo)" para DBGrids

Cuando 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.

pascal
 var
    sql : UTF8String;
    Queryu : TSQLQuery;
.....
    sql := 'SELECT cast(Name as TEXT) as Name FROM Client';
    Queryu.SQL.Text:=sql;

Ejemplo: Trabajando con Picklist, Como hacerlo de solo lectura y Como llenarlo en tiempo de ejecución

Utilizando 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.

pascal
procedure TForm1.gridSelectEditor(Sender: TObject; aCol, aRow: Integer;
  var Editor: TWinControl);
begin
  if aCol=1 then begin
    if (Editor is TCustomComboBox) then
      with Editor as TCustomComboBox do begin
        if (aRow mod 2=0) then
          Style := csDropDown
        else
          Style := csDropDownList;
        case aRow of
          1:
            Items.CommaText := 'UNO,DOS,TRES,CUATRO';
          2:
            Items.CommaText := 'A,B,C,D,E';
          3:
            Items.CommaText := 'MX,ES,NL,UK';
          4:
            Items.CommaText := 'ROJO,VERDE.AZUL,AMARILLO';
        end;
      end;
  end;
end;

Alineando texto en StringGrids

Este código muestra como utilizar diferentes alineamientos de texto en las columnas 2 y 3.

pascal

procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
  aState: TGridDrawState);
var
  MyTextStyle: TTextStyle;
begin
  if (aCol=2) or (aCol=3) then
  begin
    MyTextStyle := StringGrid1.Canvas.TextStyle;
    if aCol=2 then
      MyTextStyle.Alignment := taRightJustify 
    else 
    if aCol=3 then
      MyTextStyle.Alignment := taCenter;
    StringGrid1.Canvas.TextStyle := MyTextStyle;
  end;
end;

Multilineas en Grids, DBGrid

Este 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.

pascal

procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
  aState: TGridDrawState);
var
  MyTextStyle: TTextStyle;
begin
  if (aRow=2) or (aCol=3) then
  begin
    MyTextStyle := StringGrid1.Canvas.TextStyle;
    MyTextStyle.SingleLine := false;
    StringGrid1.Canvas.TextStyle := MyTextStyle;
  end;
end;

Validating Entered Values

Lazarus 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:

pascal

TValidateEntryEvent =
  procedure(sender: TObject; aCol, aRow: Integer; const OldValue: string; var NewValue: string) of object;

aCol,aRow son las coordenadas de la celda que se va a validar.
OldValue es el valor que había en la celda cells[aCol,aRow] antes de comenzar la validación.
NewValue es el valor que será finalmente insertado en cells[aCol,aRow].

Debido 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.

Usualment 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.

Por ejemplo, supongamos que cell[1,1] deba albergar únicamente valores 'A' o 'B'. En este caso la validación podría realizarse con:

pascal

procedure TForm1.GridValidateEntry(sender: TObject; aCol,
  aRow: Integer; const OldValue: string; var NewValue: String);
begin
  if (aCol=1) and (aRow=1) then begin
    if grid.Cells[aCol,aRow]<>'A') and grid.Cells[aCol,aRow]<>'B') then begin
     // Establecer un nuevo valor de forma que el usuario pueda presionar RETURN para continuar, por ejemplo.
      NewValue := 'A';
     // Otra opción es revertir el valor de la celda a su contenido previo (que se asume como válido).
     // NewValue := OldValue;
     // Utiliza EAbort en lugar de otro tipo de Excepción par evitar errores espureos.
     // dialog boxes and backtraces
      raise EAbort.Create('Solo se permite A o B aquí');
    end else begin
     // si no se lanza una excepción, se asume que el valor es válido, todavía si es necesario
     // el valor final puede cambiarse todavía mediante el llenado con NewValue con un valor diferente.      
     // el ordenador lo sabe mejor :)
      if grid.Cells[1,1]='A' then 
        NewValue := 'B' 
      else 
        NewValue := 'A';
    end;
  end;
end;

Ordenando columnas (Columns) o filas (Rows)

La 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.

Mediante 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.

Por 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.

pascal
// sort column 3 in ascending order
grid.SortColRow(true, 3);

// sort column 3 in descending order, skip fixed rows a top
grid.SortOrder := soDescending; // or soAscending
grid.SortColRow(true, 3, grid.FixedRows, grid.RowCount-1);

Para 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:

pascal

procedure TForm1.GridCompareCells(Sender: TObject; ACol, ARow, BCol, BRow: Integer; var Result: integer);
begin
  // El resultado será uno cualquiera de estos <0, =0, or >0 para un orden normal.
  result := StrToIntDef(Grid.Cells[ACol,ARow],0)-StrToIntDef(Grid.Cells[BCol,BRow],0);
  // Para el orden inverso simplemente niega el resultado (eg. basado en SortOrder de las grids).
  if StringGrid1.SortOrder = soDescending then
    result := -result;
end;

Puedes utilizar también OnCompareCells cuando está habilitado el reordenado automático de columna a través de la propiedad ColumnClickSorts.

Ordenando columnas o filas en una DBGrid con flejas de ordenación en la cabecera de la columna

Aquí 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.

La 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:

Para este ejemplo la TSQLQuery que utilizamos la llamamos OpenQuery.

En 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.

pascal
FLastColumn: TColumn; // Almacena la última columna que hemos ordenado en la grid.

procedure TSQLForm.ResultsGridTitleClick(Column: TColumn);
const
  ImageArrowUp=0; // Debe coincidir con la imagen de flecha arriba contenida en imagelist.
  ImageArrowDown=1; // Debe coincidir con la imagen de flecha abajo conenida en imagelist.
var
  ASC_IndexName, DESC_IndexName:string;
  procedure UpdateIndexes;
  begin
    // Ensure index defs are up to date
    OpenQuery.IndexDefs.Updated:=false; {<<<-- ¡¡¡Esta línea es crítica!!!. IndexDefs.Update no actualizará
   // si la encuentra a true, lo cual sucederá en el primer ordenamiento de columna.}
    Openquery.IndexDefs.Update;
  end;
begin
  ASC_IndexName:='ASC_'+Column.FieldName;
  DESC_IndexName:='DESC_'+Column.FieldName;
  // Los índices no pueden ordenar tipos binarios tales como ftMemo, ftBLOB
  if (Column.Field.DataType in [ftBLOB,ftMemo,ftWideMemo]) then
    exit;
  // Chequea si ya existe un índice ascendente para esta columna,
  // en caso contrario, crear uno.
  if OpenQuery.IndexDefs.IndexOf(ASC_IndexName) = -1 then
  begin
    OpenQuery.AddIndex(ASC_IndexName,column.FieldName,[]);
    UpdateIndexes; // Asegurarse de que index defs está actualizado.
  end;
  // Comprobar si ya existe un índice descendente para esta columna,
  // caso contrario crear uno.
  if OpenQuery.IndexDefs.IndexOf(DESC_IndexName) = -1 then
  begin
    OpenQuery.AddIndex(DESC_IndexName,column.FieldName,[ixDescending]);
    UpdateIndexes; // Asegurarse de que  index defs están actualizados.
  end;

  // Use the column tag to toggle ASC/DESC
  column.tag := not column.tag;
  if boolean(column.tag) then
  begin
    Column.Title.ImageIndex:=ImageArrowUp;
    Openquery.IndexName:=ASC_IndexName;
  end 
  else
  begin
    Column.Title.ImageIndex:=ImageArrowDown;
    OpenQuery.IndexName:=DESC_IndexName;
  end;
  // Remover la flecha de ordenamiento de la columna que previamente hemos ordenado.
  if (FLastColumn <> nil) and (FlastColumn <> Column) then
    FLastColumn.Title.ImageIndex:=-1;
  FLastColumn:=column;
end;

Seleccionando Registros en una DBGrid utilizando checkboxes

El 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.

Pero, ¿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.

Ten en cuenta que esta técnica necesita de Lazarus r31148 o posterior que implementan el event OnUserCheckboxState.

pascal
...
uses ..., dbgrids, stdctrls, ...

type

  { TForm1 }

  TForm1 = class(TForm)
  ...
    procedure DBGrid1CellClick(Column: TColumn);
    procedure DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  ...
  private
    RecList: TBookmarklist;
  ...
  end;

procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
  if Column.Index=1 then
    RecList.CurrentRowSelected := not RecList.CurrentRowSelected;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  RecList := TBookmarkList.Create(DbGrid1);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  RecList.Free;
end;

procedure TForm1.DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);
begin
  if RecList.CurrentRowSelected then
    AState := cbChecked
  else
    AState := cbUnchecked;
end;

Resaltando (Highlighting) la fila y columna de la celda seleccionada

En 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?.

Esta 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.

El 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:

pascal

procedure TForm1.gridBeforeSelection(Sender: TObject; aCol, aRow: Integer);
begin
  // Podemos decidir aquí si necesitamos resaltar columnas, filas o ambas.
  // En este ejemplo resaltamos ambas.
  if Grid.Col<>aCol then
  begin
    // a change on current column is detected
    grid.InvalidateCol(aCol);      // Invalida la nueva columna de celda seleccionada.
    grid.InvalidateCol(grid.Col);  // Invalida la actual columna seleccionada (esta sera la 'old')
  end;
  if Grid.Row<>aRow then
  begin
    grid.InvalidateRow(aRow);      // Invalida la nueva fila de celda seleccioanda.
    grid.InvalidateRow(grid.Row);  // Invalida la fila actual seleccionada (esta será la 'old').
  end;
end; 

procedure TForm1.gridPrepareCanvas(sender: TObject; aCol, aRow: Integer;
  aState: TGridDrawState);
begin
  if gdFixed in aState then
  begin
    if (aCol=grid.Col) or (aRow=grid.Row) then
      grid.Canvas.Brush.Color := clInactiveCaption; //Esto resaltará además las cabeceras de fila o columna.
  end else
  if gdFocused in aState then begin
    // Dejamos solas la celda actual seleccionada/enfocada.
  end else
  if (aCol=Grid.Col) or (aRow=grid.Row) then
    grid.Canvas.Brush.Color := clSkyBlue; // Resaltamos filas y columnas con el color clSkyBlue.
end;

<<>>

property OnBeforeSelection: TOnSelectEvent

property OnCompareCells: TOnCompareCells

property OnPrepareCanvas: TOnPrepareCanvasEvent

property OnDrawCell: TOnDrawCell

property OnEditButtonClick: TNotifyEvent

property OnSelection: TOnSelectEvent

property OnSelectEditor: TSelectEditorEvent

property OnTopLeftChanged: TNotifyEvent

procedure AutoAdjustColumns;

procedure BeginUpdate;

procedure Clear;

procedure DeleteColRow(IsColumn: Boolean; index: Integer);

function EditorByStyle(Style: TColumnButtonStyle): TWinControl;

procedure EndUpdate(UO: TUpdateOption); overload;

procedure EndUpdate(FullUpdate: Boolean); overload;

procedure EndUpdate; overload;

procedure ExchangeColRow(IsColumn: Boolean; index, WithIndex:Integer);

function IscellSelected(aCol,aRow: Integer): Boolean;

function IscellVisible(aCol, aRow: Integer): Boolean;

procedure LoadFromFile(FileName: string);

function MouseToCell(Mouse: TPoint): TPoint; overload;

function MouseToLogcell(Mouse: TPoint): TPoint;

function MouseToGridZone(X,Y: Integer): TGridZone;

function CellToGridZone(aCol,aRow: Integer): TGridZone;

procedure MoveColRow(IsColumn: Boolean; FromIndex, ToIndex: Integer);

procedure SaveToFile(FileName: string);

procedure SortColRow(IsColumn: Boolean; index:Integer); overload;

procedure SortColRow(IsColumn: Boolean; index,FromIndex,ToIndex: Integer); overload;

property AllowOutboundEvents:boolean;

Protegida 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).

Lo siguiente es ya antiguo pero se deja como referencia, puede que ya esté implementado y/o solucionado

Todo

known problems

29-marzo-2005:


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.

Revision 3, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.