Grids Reference Page/es

From Lazarus wiki
Jump to navigationJump to search

Deutsch (de) English (en) español (es) polski (pl) русский (ru)

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

diagrama grids.png

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 tstringgrid.png.
    • 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 tbutton.png.
    • 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.
    • StringGrid1.Cells[1,1] := '¡ Hola Mundo !';
      
    • Ejecuta el programa haciendo Click en el icono de play menu run.png.
  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:

  • Editores de celdas (Cell).
  • Comportamiento en tiempo de diseño.
  • El dibujo de las celdas tiene algunas diferencias, ver la sección de personalización de grids para más detalles.

Nuevas funcionalidades

  • Columnas personalizadas.
  • Eventos.
  • Editor de Grid.

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.

  • [Design] TitleStyle := tsStandard;
  • [Design] DefaultRowHeight := 24;
  • [Code] EditorBorderStyle := bsNone; // esto debería funcionar únicamente en windows.
  • [Code] UseXORFeatures := true;
  • [Code] AllowOutBoundEvents := False; {r10992 o posterior}
  • [Code] FastEditing := False; (soportado en dbgrid. StringGrid req. r10992 o posterior)
  • [Design] AutoAdvance := aaNone;

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:

  • Añadir 3 columnas personalizadas, la grid base estará exactamente como antes, 2 columnas fijas y 3 columnas normales.
  • Añadir una columna personalizada, la grid base tendrá ColCount := 3, esto es 2 columnas fijas y una columna normal.
  • Añadir 4 columnas personalizadas, la grid base tendrá ColCount := 6, esto es 2 columnas fijas y 4 columnas normales.

Por tanto podemos concluir que:

  • Las propiedades de columnas fijas o su cuenta no son afianzadas o modificadas respectivamente por las columnas personalizadas.
  • grid.ColCount es usualmente diferente de grid.Columns.Count (grid.ColCount=grid.Columns.Count solamente cuando FixedCols=0).

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:

  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?

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:

  • Limpia todas las celdas: grid.Clean([]); (lo mismo que grid.clean)
  • Limpia todas las celdas no fijas: grid.Clean([gzNormal]);
  • Limpia todas las celdas salvo no toca las cabeceras de las columnas: Grid.Clean([gzNormal, gzFixedRows]);

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:

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

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
  • Ejemplo para establecer: Establece el contenido de la tercera columna en la grid desde una ListBox
Grid.Cols[2] := ListBox1.Items;
  • Ejemplo para obtener: Establece el contenido de un Listbox desde la columna índice 4 de la grid.
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
  • Ejemplo para establecer: Establece el contenido de la tercera fila en la grid desde una ListBox:
Grid.Rows[2] := ListBox1.Items;
  • Ejemplo para obtener: Establece el contenido de una Listbox desde la columna índice 4 de la grid:
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;
  • Un ejemplo que no funciona, y su solución: El listado de cadenas es de solo lectura.
// 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".

  • KeyEdit: el usuario puede editar el nombre y la key.
  • KeyAdd: el usuario puede añadir keys (presionando Insert en la grid). KeyAdd requiere KeyEdit.
  • KeyDelete: el usuario puede borrar pares Key-Value (presionando Ctrl+Delete).
  • KeyUnique: si se establece, entonces las Keys deben tener nombres únicos. Un intento de entrar una key duplicada generará una excepción.

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:

  • goColMoving no está permitida en Options (no puedes establecerla).
  • goAutoAddRows solamente se puede establecer en caso de que KeyAdd se encuentre en KeyOptions. Al establecer KeyAdd automáticamente establecerá goAutoAddRows.
  • goAutoAddRowsSkipContentCheck no está permitida (por tiempo siendo así, causará un crash en TValueListeditor: necesita ser solucionado).
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:

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


grids alternatecolor.png



  • Color. Establece el color primario para dibujar el fondo de las celdas no fijas. En el dibujo de encima en color blanco Color:=clWhite.
  • 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.
  • 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.
  • TitleFont. Establece que fuente se utiliza para dibujar el texto de las celdas fijas de la fila de título.
  • TitleStyle. This property changes the 3D look of fixed cells, there are 3 settings:
    • tsLazarus. Este es el aspecto por defecto.
    • tsNative. Trata de establecer un aspecto conforme al tema actual del widgetset.
    • tsStandard. Este estilo le da un aspecto más contrastado, similar al de las grids de Delphi.
grids titlestyle.png
  • 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.
  • BorderColor. This sets the grid's border color used when Flat:=True and BorderStyle:=bsSingle;
  • 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.
  • FocusColor. El color a utilizar para dibujar la celda actualmente enfocada si UseXORFeatures no está establecida, por defecto FocusColor:=clRed.
  • FocusRectVisible. Turns on/off the drawing of focused cell.
  • GridLineColor. Color of grid lines in non fixed area.
  • 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.
  • SelectedColor. Color used to draw cell background on selected cells.
  • 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.
  • 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.
  • AutoAdvance. donde irá el cursor de celdas cuando se presione enter, o después de una edición.
  • TabAdvance. donde irá el cursor de celdas cuando se presione Tab o Shift-Tab.
  • 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.

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.
  • goFixedVertLine, goFixedHorzLine dibuja respectivamente una línea vertical u horizontal delimitan celdas o columnas en un área fija, activa por defecto.
  • 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.
  • 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).
  • goRowSelect selecciona la fila completa en lugar de celdas individuales.
  • goFixedRowNumbering si se establece, la grid realizará un numerado en su primera columna fija.
  • 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.
  • 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.

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

  • 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.
  • 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.
  • 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:
    • gdSelected La celda tendrá un aspecto de seleccionada.
    • gdFocused La celda tendrá un aspecto de enfocada.
    • gdFixed La celda tendrá que ser pintada con aspecto de celda fija.
    • gdHot El cursor del ratón se encuentra sobre esta celda, por tanto se pintará la celda con aspecto hot tracking.
    • gdPushed La celda esta siendo clickeada, por tanto se pinta con aspecto de haber sido pulsada.
  • 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:
  • 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.
  • 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?...
  • 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.
  • Text. En este punto (solamente para TStringGrid) si la propiedad DefaultDrawing vale True, realiza el pintado del texto de la celda.
  • 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).
  • 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..

Diferencias con Delphi

  • 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.
  • En Delphi, el texto de la celda se dibuja antes del evento OnDrawCell (Ver bug report #9619).

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

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

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.

  • DrawEdges: Dibuja el borde más externo de la grid.
  • 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.
    • DrawRow (virtual): Se llama para cada fila dentro de la actual ventana de visualización.
      • DrawCell (virtual): Se llama primeramente para cada celda 'normal' (i.e. no fija) en la fila.
        • PrepareCanvas (virtual): Establece los estilos de dibujados del canvas de acuredo a las propiedades visuales actuales de las celdas.
        • DrawFillRect: Dibuja el fondo de las celdas con los estilos establecidos en PrepareCanvas.
        • DrawCellGrid (virtual): Dibuja las líneas de borde de las celdas.
      • DrawFocusRect (virtual): En TCustomGrideste 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.
      • DrawCell (virtual): (ver acerca de) Se llama para cada celda fija en la línea visible dentro de ventana de visualización.
  • 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.
  • DrawBorder: Si se necesita (Flat=TRUE y BorderStyle=bsSingle), esto dibuja una línea de borde interna.

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.

  • 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).
  • DrawThemedCell (virtual): Se utiliza únicamente para las celdas fijas y si TitleStyle=tsNative. Dibuja el fondo de las celdas utilizando ThemeServices.
  • DrawColumnText (virtual): Se utiliza únicamente en las cabeceras de las columnas de las celdas.
    • 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.
    • DrawCellText : (ver mas sobre esto).
  • 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).

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:

  • DrawCell (override):
    • PrepareCanvas: llama al método heredado desde TCustomGrid.
    • DefaultDrawCell (virtual): Este método se llama solamente si el manejador de eventos para OnDrawCell NO está asignado.
      • DrawFillRect / DrawThemedCell (de TCustomGrid): Dibuja el fondo de las celdas. Si TitleStyle=tsNative, entonces el fondo de las celdas fijas se dibuja utilizando DrawThemedCell.
      • DrawCellAutonumbering (virtual): Si goFixedAutonumbering en Options, entonces el número de filas se dibujan dentro de la primera columna fija utilizando TCustomGrid.DrawCellText.
      • DrawColumnText (desde TCustomGrid): Solamente se llama para celdas de cabecera de columna (ver Additional methods to draw in TCustomGrid).
      • DrawTextInCell (desde TCustomGrid): Solamente se llama para celdas 'normales' (i.e. no fijas) (ver Additional methods to draw in TCustomGrid).
    • DrawCellGrid (virtual): Dibuja los bordes de las celdas.

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

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.

  • cbsAuto
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.
  • cbsEllipsis
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.
  • cbsNone
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.
  • cbsPickList
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.
  • cbsCheckboxColumn
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:
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.
  • cbsButtonColumn
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:

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

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:

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])

  • 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.
  • En el evento OnSelectEditor ponemos el siguiente código - adáptalo si no necesitas editar la columna lógica 3, columna física 4:
  if (Column.DesignIndex = 3) then
  begin
      GridCellMemo.BoundsRect := DbGrid.SelectedFieldRect;
      GridCellMemo.Text:=Column.Field.AsString;
      Editor := GridCellMemo;
  end;
  • 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:

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

  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

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


 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.

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.

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.

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:

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:

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.

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

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:

  • 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.
  • Hay que asegurarse de que TSQLQuery.MaxIndexesCount es suficientemente largo para contener los nuevos índices que estamos creando. Para este ejemplo lo hemos establecido a 100.
  • 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.

Para este ejemplo la TSQLQuery que utilizamos la llamamos OpenQuery.

Note-icon.png

Nota: A fecha de 21 Marzo de 2013, TSQLQuery no tiene forma de limpiar índices, pero para salir del paso, se puede establecer unidirectional a true para a continuación establecerlo a false ya que así lograremos limpiar los índices.

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.

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.

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

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

  • TInplaceEditor Support

known problems

29-marzo-2005:

  • mouse autoedit
    • linux: clicking a selected cell doesn't trigger the autoedit function (the editor lost focus inmmediatelly)
    • 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)
  • 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)
  • Resizing Columns: linux, windows. Resizing a column should not hide the editor (specially in dbgrid if we are inserting)
  • MouseWheel:
    • linux: mousewheel doesn't work as it should, (seems it's calling the default mousewheel handler)
    • windows: patch was sent.
  • Double painting: Apparently dbgrid paints the cell background twice (once with fillrect and once in textRect)
  • AutoFillColumns: sometimes the clientwidth is evaluated incorrectly (sometimes there are phantom scrollbars)