El misterio de los 200…

enero 26, 2012 en Advertencia, Artículos, Comunidad, Delphi, Enlace interesante, Entrada Diario, FireMonkey, LiveBindings, XE2

Aunque ya he comentado el contenido de esta entrada en el foro de facebook, necesitaba colgar de algún lado las fuentes que había utilizado para revisar el tema y de paso, dar la oportunidad para quien todavía no forme parte del mismo pueda acceder a su contenido, o por lo menos a las cosas que parecen interesantes. Es por esa razón que he acabado añadiéndola.

En este caso concreto, el hilo de comentarios del foro se originaba cuando uno de los compañeros, siguiendo las indicaciones del código publicado en el blog de Jim Tierney, que forma parte de los blogs de Embarcadero,  se extrañaba de que al intentar llenar los items de un componente TListBox (en tiempo de ejecución) desde una fuente de datos (un clientdataset, el numero de items añadidos al TListBox era como máximo igual o menor a 200. Y eso sucedía aun cuando dicha fuente de datos contuviera una cantidad mayor.

Esta es la entrada en la que me he basado para reproducir el problema y comprenderlo.

LiveBindings: Code to create TBindLink and fill a Listbox

Creo que lo mas interesante de estas lineas no es ya la corrección que se ha hecho para solucionar el problema, que solo ha consistido en añadir la linea de asignación en el procedimiento FillList( ) de la unidad UMain.pas

LBindList.BufferCount:= ARecordCount;

sino en destacar el punto que originaba el problema:

constructor TBindScopeDBEnumerator.Create(ABindScope: TCustomBindScopeDB;
  const AMemberName: string; ABufferCount: Integer);
begin
  FBindScope := ABindScope;
  FMemberName := AMemberName;
  FSaveActiveRecord := FBindScope.FDataLink.ActiveRecord;
  FNextRecord := FSaveActiveRecord;
  if ABufferCount > 0 then
    FBindScope.FDataLink.BufferCount := ABufferCount
  else
    FBindScope.FDataLink.BufferCount := 200;  // default to max 200 records in buffer
end;

 Al final, ese era el motivo por el que, no estando definido el valor del campo BufferCount en TBindList, cualquier movimiento hacia adelante de la estructura del enumerador, comprobaba si habia llegado al ultimo registro por lo que aunque existiera una cantida mayor en el dataset, el enlace le indicaba que había llegado al último.

:-)

En fin… cosas de los valores por defecto que supongo que sería fijado por algún motivo, porque de hecho el comentario en la misma linea corrobora que se hizo por alguna razón que ahora mismo ciertamente no comprendo.

Lo mas gracioso de todo es que pienso que esto debería por la forma en que se ha planteado afectar en tiempo de diseño por lo que quizás deberíamos comprobar que al crear una relación TBindList desde el editor de expresiones, en tiempo de diseño, el valor del campo en cuestión es correcto. Podéis hacer la prueba y comentamos en el foro. Para probarlo, simplemente acceded a la propiedad LiveBindings del TListBox y cread un nuevo enlace de tipo TBindList. Y seguidamente definid para la propiedad Format un nuevo item con los valores indicados en la rutina FillList( ). En las pruebas que he hecho, también se reproduce el error.

Tened en cuenta este punto para no caer en el problema.

 

 

unit UMain;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, Data.DB, Data.Win.ADODB,
  Datasnap.DBClient, FMX.Layouts, FMX.ListBox, FMX.Bind.Editors, Data.Bind.Components,
  Data.Bind.DBScope, Datasnap.Provider, Data.Bind.EngExt, Fmx.Bind.DBEngExt;

type
  TfrmFillListBox = class(TForm)
    lbxData: TListBox;
    bnFill: TButton;
    bnClear: TButton;
    cdsData: TClientDataSet;
    Conexion: TADOConnection;
    qData: TADOTable;
    dsData: TDataSource;
    dspData: TDataSetProvider;
    cdsDataOrderNo: TFloatField;
    cdsDataCustNo: TFloatField;
    BindScopeDB1: TBindScopeDB;
    lbRecordCount: TLabel;
    lbItemsCount: TLabel;
    BindingsList1: TBindingsList;
    procedure bnClearClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure bnFillClick(Sender: TObject);
  private
    { Private declarations }
    procedure FillLabelRecordCount;
    procedure FillLabelItemsCount;
  public
    { Public declarations }
  end;

var
  frmFillListBox: TfrmFillListBox;

implementation

{$R *.fmx}

//fuente del procedimiento: http://blogs.embarcadero.com/jimtierney
//         http://blogs.embarcadero.com/jimtierney/2011/10/03/31601
//
//  El procedimiento encapsula los pasos para rellenar distintos
//  tipos de controles, siguiendo lo que haria el usuario en tiempo de
//  diseño. Es util para el tiempo de ejecución
//
procedure FillList(AControl: TComponent; const AControlExpression: string;
  ASource: TBaseBindScopeComponent; const ASourceExpression: string; ARecordCount: Integer; const ASourceMemberName: string = '');
var
  LBindList: TBindList;
begin
  LBindList := TBindList.Create(nil);
  try
    // Turn off auto properties.
    LBindList.AutoFill := False;
    LBindList.AutoActivate := False;
    LBindList.ControlComponent := AControl;
    LBindList.SourceComponent := ASource;
    LBindList.SourceMemberName := ASourceMemberName;
    LBindList.BufferCount:= ARecordCount; //<- Linea añadida
    with LBindList.FormatExpressions.AddExpression do
    begin
      SourceExpression := ASourceExpression;
      ControlExpression := AControlExpression;
    end;
    LBindList.FillList;
  finally
    LBindList.Free;
  end;
end;

procedure TfrmFillListBox.bnClearClick(Sender: TObject);
begin
  lbxData.Clear;
  FillLabelItemsCount;
  FillLabelRecordCount;
end;

procedure TfrmFillListBox.bnFillClick(Sender: TObject);
begin
  FillList(lbxData, 'Text', BindScopeDB1, 'AsString', dsData.DataSet.RecordCount, 'OrderNo', );
  FillLabelItemsCount;
  FillLabelRecordCount;
end;

procedure TfrmFillListBox.FillLabelItemsCount;
begin
  lbItemsCount.Text:=  'Items.Count: '+IntToStr(lbxData.Items.Count);
end;

procedure TfrmFillListBox.FillLabelRecordCount;
begin
  lbRecordCount.Text:=  'RecordCount: '+IntToStr(cdsData.RecordCount);
end;

procedure TfrmFillListBox.FormCreate(Sender: TObject);

begin
  Conexion.ConnectionString:= 'Provider=Microsoft.Jet.OLEDB.4.0;'+
                              'Data Source=C:\Program Files\Common Files\CodeGear Shared\Data\dbdemos.mdb;'+
                              'Persist Security Info=False';
  cdsData.Open;

  FillLabelItemsCount;
  FillLabelRecordCount;
end;

end.

Nada mas por comentar. Si deseáis ver el ejemplo podéis acceder al siguiente enlace:

Descargar fuentes

Aviso sobre el grupo de Facebook “Delphi Solidario”

septiembre 6, 2011 en Advertencia, Delphi, Entrada Diario, Facebook, Noticias, Recordatorio

Hola a todos:

Comentaros que finalmente tomé la decisión de volver a crear el grupo de Facebook, “Delphi Solidario” de acuerdo a la nueva estructura de grupos de esta red social  , (decisión que había demorado durante meses), y para ello, se ha hecho necesario dar de baja todas los miembros que formaban parte del antiguo grupo. Si observáis que ha desaparecido vuestra membresía ya conoceis la razón. ¡No vayáis a pensar que es por otro motivo!

Para poder eliminar un grupo antiguo hay que eliminar todos los miembros, momento en el que Facebook lo elimina del directorio de grupos. Podría haber simultaneado durante un tiempo ambos grupos e ir traspasando gradualmente a los miembros pero creo que no valía la pena, ya que no eramos tantos (aproximadamente unos 50 compañeros).

A los miembros que he podido dar de alta directamente, porque ya formaban parte del circulo de amigos, los he incluido (espero no haber olvidado a ninguno). Os pido, al resto, que os vayáis uniendo al grupo.

Esta es la dirección del grupo de facebook “Delphi Solidario”:




http://www.facebook.com/groups/delphisolidario/

(recordad que también existe el enlace en la barra lateral del blog)

Respecto al motivo del cambio al nuevo sistema de grupos, es básicamente por el sistema de publicación, que tiene mejoras respecto al antiguo sistema de grupos. Al final, eras mas las ventajas que los inconvenientes y creo que valía la pena.

Sed libres de participar en el grupo, añadiendo los enlaces y publicaciones que creáis conveniente resaltar. Es un grupo abierto y libre (con el respeto a unas mínimas normas de convivencia que todo el mundo ya conoce)

Un saludo y gracias por participar,

Salvador

 

 

 

 

El mundo al revés

marzo 8, 2011 en Advertencia, Delphi, Enlace interesante, Entrada Diario, Nos deja la semana...

Durante el fin de semana, como casi siempre hago, dejé varios apuntes que me parecían interesantes señalados en la barra de favoritos. Muchos de esos apuntes, o enlaces, son páginas  cuyos blogs ya existen en los enlaces de Delphi Básico. Otros, son simplemente pág¡nas en las que cae uno de forma accidental tras revisar los twitters nuevos o los enlaces resaltados de cualquier otra aplicación social. De una forma u otra, la idea es  tenerlos a mano y compartirlos, bien en el blog, bien en facebook o twitter.

Tras escribir el comentario sobre el Hotfix que ya tenemos disponible, me quedaban resaltar algunos de esos apuntes de la semana que me parecían interesantes.

El primero a destacar podría ser la calidad e interés que descubren los últimos artículos de German (Neftalí y en general, todos los que se recopilan en su blog):

A me ha parecido particularmente util y didáctico el último, que aborda el tema de generar los puntos de ruta en Google Maps Generar ficheros KML de rutas; Tracks en Google Maps, pero igualmente podía haber destacado el anterior en el que muestra sus busquedas preferidas respecto a íconos (tema realmente importante para mejorar el aspecto de nuestras aplicaciones), la ultima actualización de las RX para XE o finalmente, por no alargar la lista mas, la descarga de imagenes a traves del componente TidHTTP (TidHTTP; Descargar imágenes de una Web). Germán está siguendo una linea donde destaca la regularidad y la constancia, asi como la claridad en la exposición de los temas.

El segundo punto a destacar, creo que podría ser el enorme esfuerzo de difusión de tips y consultas de foros que se está abordando por parte de los compañeros de la Comunidad de Delphiaccess, que se están volcando por hacer públicas muchas de esas respuestas que solucionaban problemas (también a través de distintas redes sociales). Enfoques nuevos para desafios nuevos… Me gusta la idea de alejarse de esa visión estática de los foros, tan tradicional, que han buscado principalmente en los foros una forma sencilla de incrementar las visitas al dominio y poco mas. Así que, punto arriba (+1) para DelphiAccess en ese intento de convertirse en referencia para nuestra Comunidad.

Para el tercer punto, veo anotado este enlace http://delphibugs.blogspot.com/, que visité durante el fin de semana. El blog se centra en describir algunos bugs que el autor ha destacado, siendo un blog muy reciente, ya que los artículos escritos son del año anterior.  Pero bueno, la fecha no es siempre indicativo de que sea realmente interesante o no lo sea, aunque tambien es verdad siendo realistas que muchos de estos blogs nacen y desaparecen con la misma rapidez, una vez se ha satisfecho el deseo irrefenable del comentario. La última entrada http://delphibugs.blogspot.com/2010/09/xe-new-bug-1-wild-memory-eating.html nos comenta de forma visual incluso, como se dispara el consumo de memoria al ejecutarse el Code Insight (si se encuentra activo ErrorInsight).

Bueno… yo lo he intentado reproducir siguiendo los mismos pasos que él, para ver si era cierto o no y en que condiciones se producía y comentarlo aquí, pero a pesar de todas las pruebas no he podido, no ha habido forma de comprobar la veracidad del comentario, por lo que para mi, de momento lo dejo como caso cerrado. Lo que si se le puede reprochar, es que este tipo de anotaciones o entradas, deberían ir acompañadas de algun dato que identifique la versión sobre la que se reproduce el error (yo no lo he encontrado por ninguna parte). Le supongo la buena fe de la publicación pero sirve de poco fuera del contexto del QC.

Seguimos nuestro camino con un blog (amor/odio a Delphi al 50%) http://delphihaters.blogspot.com/, dentro de una publicación que entra como un elefante en una cacharrería a despachar sobre todo lo humano y lo divino, especialmente beligerante en lo que afecta a la ruta de Embarcadero.  A mi particularmente me ha parecido interesante su lectura por el hecho de suponer un contrapunto con las voces que siempre escuchamos en nuestro entorno, lo cual no significa que yo asienta o disienta del contenido expresado en su blog. No obstante, Delphi Básico se ha caracterizado siempre por intentar ser respetuoso con otras opiniones, razón por la que pensé que debía resaltarlo. 

El cuarto punto, ya para finalizar la entrada, es el que hace referencia al post An ugly alternative to interface to object casting de Barry Kelly, ingeniero de software que trabaja para Embarcadero en el compilador de Delphi. Al inicio de la entrada, se puede leer su motivación al escribirla:

I was answering a question on Stack Overflow, but the user didn’t have the latest version of Delphi. My answer included converting an interface to an object instance, which is made possible with the as cast on interfaces in recent Delphi versions. But there is another way of doing it, exploiting the regularity Delphi interface vtable implementations

Es decir, que esta entrada venía a extender algunas respuesta en Stack Overflow que relataban como acceder al objeto que implementaba la interfaz, que se habían centrado básicamente en el uso del operador as.  He remarcado But there is another way of doing it, para que se vea claramente que lo expuesto en la entrada y que a mi, personalmente me pareció muy interesante, es que la via alternativa que analiza, se basa en un conocimiento muy cercano (íntimo) de la estructura de la vtable con los métodos del objeto e interfaces que implementa.

Ese tipo de lectura siempre me fascinó porque desmonta el entramado del escenario y te muestra los bastidores en contrapunto de los articulos que muestran solo la parte externa del “artilugio”  :-D    Así que por unos minutos me volvía sentir como Indiana Jones en busqueda del misterioso tesoro y látigo en mano fui recorriendo el código para comprenderlo. Para ampliar información y definitivamente comprender el articulo, si os sucede como a mi que dejó algunas inquietudes, os recomiendo la lectura de Delphi interfaces on binary level de ksTools, que fue respuesta al articulo de Barry Kelly.

Quizás el título de mi entrada vino inspirado por el artículo de Barry Kelly. Pero eso es menos interesante…  ;-)  

Anotad el enlace a Stack Overflow, los que no lo teníais, ya que hay muchas respuestas y preguntas dirigidas y respondidas por expertos y gurus de la Comunidad. Un enlace muy interesante recomendable 100%.

Y por esta semana, poco mas que contar.

No me saques los colores

agosto 27, 2010 en Advertencia, Artículos, Delphi, Entrada Diario, Taller práctico

¿Qué tal estáis…?

Volvemos tras un verano de calor… Esta misma noche fue una de las mas calurosas que recuerdo. De hecho, ayer mismo, el termómetro se acerco a los 44º y la tarde fue asfixiante. La disfruté visitando una exposición temporal sobre arqueología Egipcia que se celebra en Alicante, en el Museo Arqueológico Marq, y aunque la capital mantiene unas temperaturas bastantes suaves y agradables por la cercanía del mar, hacía calor. Muy recomendable esta exposición si estáis en la zona, ya que no es demasiado habitual para nosotros ver de cerca una momia y conocer los ritos funerarios de la fascinante y enigmática civilizacion egipcia (El enigma de la momia). Aunque, para ser justos, el visitante se queda con ganas de mas, y no imagino lo agradable y enriquecedor que puede ser conocer el total de lo expuesto en el Louvre, puesto que lo visto en Alicante es tan solo una parte pequeña.

Pero dejemos las momias por el momento y permitid que me remonte varios días atrás, durante la semana anterior, cuando estaba pensando con qué iba a abrir el blog, tras las vacaciones…  ;-)

Mentiría si os dijera que no me sobresaltó el móvil. Mientras estamos concentrados en resolver nuestras tareas rutinarias,  a veces perdemos la  noción temporal y espacial, abstraídos en seguir el hilo al código escrito y entender lo que en ese momento falla, o no hace lo que deseamos, que también sucede. Así que al volver a la realidad, tras la sacudida de la melodía marchosa que había alertado a media oficina, lo primero que se escuchó a medida que iba acercándome el auricular a mi oreja, era una serie de gruñidos ininteligibles que iban creciendo, hasta obligarme a retirarlo prudencialmente. No estaba demasiado contento el interlocutor desconocido o quizás se hubiera atragantado en un almuerzo improvisado. :-?

-grrrrrrrrr… me cago en su p. madre….  me cago en tooo… en tooooo… grrrrrrrrrrr… -iba adivinando en el tumultuoso sonido de mi auricular. Se oían de fondo como golpes secos que yo imaginaba eran los puños de aquella persona contra alguna mesa…

-¿si..? ¿Pero quién eres… ?- pregunté esperando quizás que fuera alguna equivocación o malentendido o cruce de lineas.

-¡Manuel!… -respondió atropelladamente el desconocido- ¡qué soy yo!… Manuel… -¿Dónde estás ahora? ¿Puedes hablar?

-Joder… -asentí con la cabeza como si Manuel pudiera verme- Sí puedo hablar. ¿qué pasa?

-Nada tío… ¡Que estoy intentando cambiar el color del título de una columna del dbgrid y me dice que naranjas de la china… ¡Qué asco…!. Cuatro horas perdí ayer en la empresa con esta tontería. ¡Increible! Si tuviera cerca a esos de la Barca les… (ruido de fondo, ggggggererereerttweqwqfgafsdsfoooofsdfoafodrrgrgrgrgrgrgrgr)…

Manuel aumentaba su indignación a medida que me narraba sus cuitas.

-¡No jodas! -exclamé con aspavientos.

Y ahí se quedó la cosa…

Así que tras colgar, mi curiosidad por ver cual era el problema iba creciendo. Revisé la nota que había escrito y la guarde en la cartera hasta que al llegar a casa pudiera ver de cerca el problema. Con independencia de que no tenga mayor importancia, pues a fin de cuentas es trivial que el usuario no pueda cambiar el color del titulo de la columna de un tdbgrid, no parece algo de vida o muerte. Eso si, sí parece cierto que la mayoría de las veces, resolvemos cosas de esa forma. Otras veces ni siquiera se resuelven y nos vemos obligados a tomar atajos o parchear para evitar los problemas, o buscamos componentes que resuelvan eso que el que tenemos en las manos no nos da. A veces somos nosotros los que estamos a un lado de la linea y en ocasiones estamos en el otro lado. Y los foros y las relaciones de amistad cubren para ser justos, muchos de estos problemas que o bien no están bien documentados ni registrados o bien forman parte de expedientes X, dignos de Iker Jimenez en 4º Milenio…  :-D

(por cierto… y hablando de otra cosa y ya que estáis aquí:  ya veis que se ha cambiado el tema de wordpress en el blog… en mi pensamiento: el móvil y los posibles conexiones al blog desde éstos, en un Mundo donde cada vez tiene mas peso esta posibilidad. Ha supuesto perder algo de estética. El tema anterior me gustaba). También ha tenido peso intentar conseguir menor tiempo de carga de la página. Eliminar pluggins que retrasaban la carga de la página: ¿seguridad frente a velocidad?, etc. etc. Aunque ni yo mismo me lo crea, llevo varias semanas con estos detalles. ¡Increible, che!. También y ya para acabar, ando en el intento de enlazar mi FaceBook con el blog, que me permitirá compartir las entradas también desde la cuenta que he abierto a tal efecto. Ya he visto que existen bastantes pluggins aunque por problemas puntuales de Facebook todavía no he podido crear una aplicación para activar el conector).
Veamos… ¿por dónde iba? Ahhh. Sí.

Lo del color del título de la columna en el componente TDBGrid debe ser una tontería pero por lo que he podido ver funcionaba hasta Delphi 2007. No lo he probado en Delphi 2009, pero lo que es en Delphi 2010 parece que da problemas. Introdujeron algunos cambios para incorporar los temas de windows y los gradiantes, con el fin de mejorar la estética de la rejilla de datos, pero lo que se consigue es que con el flag gdsClassic todas las columnas se repinten del color fijo y no del color persistente individual.

Cuando llegué a casa, y con ánimo de echarle una mano a Manuel, escribí unas lineas muy sencillas de código para reproducir el problema, y las probé tanto en Delphi 2007 como en Delphi 2010.

Descargar el código: Aquí.

Esta es una imagen que aparecen las dos ventanas que estaba contrastando.

captura del problema


procedure TForm1.bnColorClick(Sender: TObject);
begin
Rejilla.Columns[0].Title.Color:= clRed;
end;

Pues lo dicho. Algo tan sencillo como eso, no funcionaba en Delphi 2010 tras los cambios. O por lo menos, yo tampoco lo sabía corregir, si es que era un tema de cambiar alguna de las opciones del componente.

Lo siguiente… ir poniendo puntos de parada para intentar comprender cual era el problema y de lo visto, creo entender que una de las lineas introduce a piñon el color fijo (fixedcolor), error que se produce al invocar la siguiente llamada:

DrawCellBackground(TitleRect, FixedColor, AState, ACol, ARow – FTitleOffset);

dentro del código del procedimiento

procedure DrawTitleCell(ACol, ARow: Integer; Column: TColumn; var AState: TGridDrawState);

Éste (DrawTitleCell), declarado en el interior del método  TCustomDBGrid.DrawCell, queda acotado al ámbito local del mismo.

A lo tonto a lo tonto (expresión que expresa que aunque parezca increible sucedió  :-D) me zampé parte de la tarde y de la noche jugueteando con esta tontería.  ;-)  Acompañado de un vaso grande de Horchata, muy rica, extraordinariamente rica, y de varios fartons y congrets, deliciosos… Ummmmmmmm.. (me puse como el tio quico!!!!!!) .

(*) Fartons (una imagen para que sepais que es un farton)  ;-)

Y resolví finalmente por crear un descendiente de la clase TDBGrid añadiendo un método que  pudiera redefinir DrawCellBackground y poder así cambiar el comportamiento, ya que parecía entre las distintas posibilidades la mas sencilla . El crear un descendiente era simplemente porque este método es declarado como protegido.

Escribí unas lineas de código, que veis a continuación.

unit ColorearColumnaD2010;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Grids, DBGrids, ADODB, DB, DBClient, TConnect, Provider, StdCtrls,
  ExtCtrls, DBCtrls;

const
  kseparacion = 5;
type
  TRejilla = class(TDBGrid)
  protected
    procedure DrawCellBackground(const ARect: TRect; AColor: TColor;
  AState: TGridDrawState; ACol, ARow: Integer); override;
  end;

  TForm1 = class(TForm)
    dsData: TDataSource;
    Button1: TButton;
    data: TClientDataSet;
    dataID: TIntegerField;
    Navegador: TDBNavigator;
    procedure RejillaTitleClick(Column: TColumn);
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    Rejilla: TRejilla;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Rejilla.Columns[0].Title.Color:= clGreen;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  fColumn: TColumn;
begin
  Rejilla:= TRejilla.Create(self);
  Rejilla.Parent:= self;
  Rejilla.Top:= Navegador.Top + Navegador.Height + Kseparacion;
  Rejilla.Left:= Navegador.Left;
  Rejilla.DataSource:= dsData;
  Rejilla.DrawingStyle:= gdsClassic;
  Rejilla.OnTitleClick:= RejillaTitleClick;
  data.Open;
  fColumn:= Rejilla.Columns.Add;
  fColumn.FieldName:= 'ID';
  fColumn.Title.Color:= clRed;
end;

procedure TForm1.RejillaTitleClick(Column: TColumn);
begin
  ShowMessage('Pulsaste la columna '+ IntToStr(Column.ID));
end;

{ TRejilla }

procedure TRejilla.DrawCellBackground(const ARect: TRect; AColor: TColor;
  AState: TGridDrawState; ACol, ARow: Integer);
begin
  if ACol < 0 then
    inherited DrawCellBackground(ARect, AColor, AState, ACol, ARow)
  else inherited DrawCellBackground(ARect, Columns[ACol].Title.Color, AState, ACol, ARow);
end;

end.

Para aclarar un poco, simplemente comentar que dado que no lo tenía instalado en la paleta de componentes, existen esas lineas de creación y asignación de propiedades en el evento de OnCreate del formulario. Otra opción sencilla, hubiera sido interponer la clase. Ya lo vimos en uno de los capítulos de la serie de los mayores, cuando hablábamos de la unidad UGrids.

Respecto al método sobrescrito, solo si el índice del parámetro que representa a la columna (ACol) es menor que cero se deja el color por defecto (estaríamos dibujando sobre la columna de indicadores). En caso contrario, entregamos el color del título de la columna, para que se repinte de la forma deseada.

Ahhh… la conversación figurada que he descrito con mi amigo es como podéis imaginar figurada… :-) para darle un poco de emoción y vida a esta entrada. Pero sí es cierto que él andaba molesto por la perdida de tiempo que suponen este tipo de problemas. Lo cual nos lleva a reflexionar sobre algo que quizás pase desapercibido pero que en mi opinión nos está afectando y es el mantenimiento y mejora de la propia VCL. A nadie se le escapa que a pesar de los esfuerzos que se han podido hacer en ese sentido, existen todavía muchos trabajo que hacer.

Este tema sí me parece importante. Tanto como lo pueda ser que el día de mañana aparezca Delphi para 64 bits o para multiplataforma. Seguro que vosotros tenéis opinión formada sobre eso. En la mía quedan preguntas en el aire que son dificiles de responder…

¿Seguir avanzando y dotar al entorno de nueva funcionalidad sin reforzar el mantenimiento de lo construido a la fecha?

¿Qué papel juega la comunidad realmente en todo esto?

¿Qué responsabilidad moral de la empresa sobre lo que sabe que no funciona?

Se me ocurren mil preguntas, a cada cual más difícil de responder…

TDBLookupComboBox al escenario (Parte I)

junio 17, 2010 en Advertencia, Código, Delphi, Entrada Diario, Taller práctico

Llevaba varias semanas con la idea de hacer un par de comentarios breves sobre el componente TDBLookupComboBox y mira por donde, hoy parece que tengo unos minutos que voy a intentar aprovechar.

Así que vamos a ello. La idea es ayudar con algunas ideas a los compañeros que dan sus primeros pasos.

Este componente es bastante clásico (no es nuevo). Y está ubicado en la paleta “DataControl”, la paleta que Borland reservó en sus primeros días para alojar todos los controles de datos y se justifica, en un contexto donde el usuario selecciona un item entre los valores mostrados en una lista ligada a un conjunto de datos, para asignar un valor (como resultado de esta selección), en un campo del dataset destino. Así pues, este valor que va a ser asignado, es por regla general distinto del valor que es mostrado al usuario y también siguiendo el mismo razonamiento, formaría parte de un dato significativo de una tabla maestra respecto a su detalle (eso es algo lógico ¿no?). Digo “por regla general” ya que realmente no existe ninguna restricción que impida que los campos Keyfield y ListField se asignen al mismo nombre de campo pero esto no tendría demasiado sentido. Sería similar a la funcionalidad ofrecida por el componente TDbComboBox, salvando el hecho de que este obtiene su lista de selección sobre una lista de cadenas, cargada en memoria mediante algunos de los métodos que publica la clase TStrings.

Por poner un ejemplo que nos sirva de referencia en esta entrada, podemos imaginar una tabla de avisos muy sencilla, que nos servirá como recordatorio de una tarea, y en la que podemos anotar la fecha-hora,  el cliente, el comentario o la observación a recordar y si dicha tarea está activa o no lo está. La estructura habitual de un tabla así, podría ser similar a la generada por este script de sql:

CREATE TABLE [dbo].[Avisos] (
  [IDAviso] int IDENTITY(1, 1) NOT NULL,
  [IDCliente] int NOT NULL,
  [Fecha] datetime NOT NULL,
  [Comentario] varchar(80) NULL,
  [Activo] char(1) NULL,
  PRIMARY KEY CLUSTERED ([IDAviso])
)
ON [PRIMARY]
GO

Y siguiendo el ejemplo, se supone que debería existir otra tabla ligada a los clientes, que vamos a limitar a tres campos que puedan ser significativos, en este contexto (el identificador del cliente, el código del cliente y finalmente su nombre).

CREATE TABLE [dbo].[CLIENTES] (
  [IDCliente] int IDENTITY(1, 1) NOT NULL,
  [Codigo] int NOT NULL,
  [Nombre] varchar(40) NOT NULL,
  PRIMARY KEY CLUSTERED ([IDCliente])
)
ON [PRIMARY]
GO

Podría además existir definida en la base de datos la relación de clave ajena que permite vincular los valores almacenados en el campo IDCliente de la tabla avisos con los valores existentes en la tabla de clientes, pero a efectos de estos comentarios no es algo relevante, por lo que yo creo que podemos obviarlo y simplemente suponer que existe.

Para acompañar la entrada, he querido también preparar un pequeño ejemplo que recree lo que estamos compartiendo pero, como no he querido usar una conexión a base de datos, para no complicar el escenario con algo que tampoco es imprescindible, he añadido en el módulo de código un procedimiento para recrear la creación de nuestros datasets, que se ejecuta durante la creación del formulario.

procedure TMain.InicializacionDatosEjemplo;

Ya tenemos casi todos los ingredientes.

Si ojeais el procedimiento para inicializar los datos y la estructura del dataset, vereis que la tabla de Avisos contiene tanto el IDCliente como los campos Codigo y Nombre, aunque no pertenezcan a dicha tabla sino a la de Clientes.  Os incluyo una imagen que os muestra los campos que contienen cada ClientDataSet (en el codigo de inicialización he incluido para que sea mas didáctico, unas rutinas para crear los campos persistentes que están comentadas, ya que la creación la hice en tiempo de diseño)

Convenimos que podría ser algo razonable, fruto de una consulta sql similar a:

SELECT
  dbo.Avisos.IDAviso,
  dbo.Avisos.IDCliente,
  dbo.CLIENTES.Codigo as CodigoCliente,
  dbo.CLIENTES.Nombre as NombreCliente,
  dbo.Avisos.Fecha,
  dbo.Avisos.Comentario,
  dbo.Avisos.Activo
FROM
dbo.Avisos INNER JOIN dbo.Clientes ON
(dbo.CLIENTES.IDCliente = dbo.Avisos.IDCliente)

Esto no es nada extraño. Sería un efecto similar al haber definido los campos CodigoCliente y NombreCliente como Lookup vinculados a una consulta abierta al dataset del cliente, en lugar de haber sido creados como tipo Data, que es el caso referido. No… no digo que esté mal o bien  :-)  y si estas dando tus primeros pasos quizás lo veas hasta lógico que sea indicado. A mi también me pasaba… Pero la picaresca es muy útil y nos basta una pequeña “trampa” legal para evitar que los susodichos campos sean actualizables, obra y gracia de nuestro TClientDataSet. ¿Hay alguna razón de que todos los campos sean actualizables, contrastables y participen en la sentencia retornada a la base de datos…?

Ya sabeis por donde voy ¿no? Nos basta una simple asignacion a false en los valores pfInUpdate y pfInWhere de las opciones providerflags de cada uno de los dos campos. Y quedan los dos a nuestra merced, para que puedan ser alterados durante el proceso de grabación y confirmación de cambios (sin que afecten a nada). Eso sí… la sentencia sql debería referir en la parte que vincula una tabla con otra tras el FROM, siempre en primer lugar la tabla que va a ser actualizada, que en este caso concreto es la tabla de Avisos.

Seguimos avanzado en el ejemplo y diseñamos un formulario, que va a mostrar una rejilla en la parte superior y lo que pudiera ser una ficha editable en la parte inferior. El poner la rejilla es para hacerlo un poco más gráfico y visual y que se aprecie como dos cosas distintas, puesto que en todos los frameworks que hemos compartidos han existido estos dos elementos.

Esta es la imagen de nuestro único formulario:

Descargar el fichero fuente

Ahora nos resta escribir unas lineas de código, en el Evento OnValidate del campo CodigoCliente:

procedure TMain.cdsAvisosCodigoClienteValidate(Sender: TField);
begin
  if not Sender.IsNull then
  begin
     if cdsClientes.Locate('Codigo', Sender.Value, []) then
     begin
       cdsAvisos.FieldByName('IDCliente').Value:= cdsClientes.FieldByName('IDCliente').Value;
       cdsAvisos.FieldByName('NombreCliente').Value:= cdsClientes.FieldByName('Nombre').Value;
     end
     else raise Exception.Create('No se ha encontrado el cliente');
  end
  else begin
     cdsAvisos.FieldByName('IDCliente').Clear;
     cdsAvisos.FieldByName('NombreCliente').Clear;
  end;
end;

El efecto que estamos logrando es el de coordinar de una forma sencilla, tanto que pueda haber sido usado el selector de clientes como la casilla en la que el usuario va a introducir el código, dato que en un porcentaje alto de casos es sabido de antemano por el usuario.

Eso es algo bastante normal y corriente, que existan junto con la clave primaria, otras claves naturales que conviven y se garantizan mediante indices únicos. Imaginemos una tabla de pedidos (por poner un ejemplo claro), con una clave primaria que podría ser de tipo entero, incremental. Y una clave natural formada por el Ejercicio, la Empresa y el numero de pedido (y quizás la serie también si la empresa hiciera uso de series). La clave primaria en esos casos nunca es del dominio del usuario sino que es usada internamente por la aplicación para apuntar a los registros, mientras que la clave natural, en cambió, si es usada por el usuario al interactuar con el interfaz de la aplicación.

Pensareis: si hubieramos apuntado el campo KeyField de nuestro TDBLookupComboBox a IDCliente tambien hubiera funcionado sin ese código añadido en el evento OnValidate ¿no?.  Pues sí, pero fácilmente crearíais referencias circulares entre ambos, al intentar coexistir y modificarse uno a otro tras la acción del usuario. Al apuntar los dos componentes a CodigoCliente, cualquier modificación en uno de ellos, automáticamente modificará al otro.

Finalmente, habría que asignar los valores de las propiedades, tanto en el TDBEdit como en el TDBLookupComboBox. Veamoslo:

Primero en el TDBEdit. (el punto rojo nos indica la asignación)

También modificamos los valores de las propiedades implicadas en el selector. La conexión con el componente ClientDataSet vinculado a la tabla clientes se hace a través de ListSource, ListField y KeyField.

Esta podría ser una captura de la ejecución del código fuente que, aunque no resulte deslumbrante puesto que estamos compartiendo algo muy básico, al menos sí puede ser de utilidad para quien haya tenido dudas porque se enfrente por primera vez a montar estructuras similares. Descargad el código y comentáis lo que creáis oportuno.

Por hoy nada mas. Espero que os haya servido de ayuda.

La parte II, que posiblemente cerrará estos comentarios,  nos valdrá para reconocer algunas carencias del componente y ahondar en estas reflexiones que compartimos.

Shifting TFields in TDataSets Bound to TDBGrids…

marzo 24, 2010 en Advertencia, Artículos, ¿Sabías que...?, ¿Sabías que...? [Delphi], Consejo, Delphi, Entrada Diario, traducciones

Esta vez, he seleccionado uno de los artículos del blog de Cary Jensen que me ha parecido especialmente interesante para compartirlo con vosotros. En el artículo, Cary Jensen comenta con sus lectores, un posible bug ocasionado por el comportamiento de los campos persistentes del DataSet (luego se verá en el articulo, comentado y reflexionado por el, que no es tanto un error de código). De cualquier forma, sí me ha parecido interesante pues es algo que debería tenerse en cuenta.

Os explico:  Accidentalmente, se da cuenta que al ser movidas las columnas de una rejilla de datos, una facultad que tiene per se de forma automática al ser creadas, el orden en los campos del dataset es alterado. ¿En que casos? Bueno, esto solo sucede cuando no existen los campos persistentes en el componente TDBGrid y puede generar errores si hemos referenciado llamadas a los campos persistentes del dataset mediante el array TField usando el indice de acceso. Cary, a tenor de lo visto reflexiona en la entrada sobre este punto y las posibles soluciones.

Yo tampoco creo que sea un bug. Aunque particularmente a mi sí me hubiera parecido acertado bloquear la funcionalidad de mover las columnas y solo permitirla en el caso de que hubieran sido creadas, de forma que no hubiera podido existir ese potencial error. Pero bueno… es mi opinión.

Ahh. Como siempre comento, perdonad los posibles errores en la traducción que he intentado que fuera lo menos literal posible.

Vamos a ello:

Shifting TFields in TDataSets Bound to TDBGrids: A Potential Source of Bugs in Your Code

Autor: Cary Jensen – Enero 26, 2010

http://caryjensen.blogspot.com/2010/01/shifting-tfields-in-tdatasets-bound-to.html

He estado trabajando con Delphi desde el principio, con particular énfasis en el desarrollo de bases de datos. Como resultado de ello, no es demasiado frecuente que yo encontrase un comportamiento de los componentes relacionados con los datos que me sorprendiera. Bien. Ha sucedido así en el último mes. Y lo que yo observé  puede ser el origen de errores potencialmente desastrosos, aunque infrecuentes, en un gran numero de aplicaciones de bases de datos con Delphi.

Esto es lo que yo pude observar: Los TFields en un TDataSet abierto, cambiaron de orden en tiempo de ejecución. Especificamente, un TField que estaba originalmente en la posicion cero (DataSet.Fields[0]), en el momento en el que se habia creado el TDataSet, estaba en una posición diferente en el array de campos un pequeño tiempo después. Yo descubrí este comportamiento cuando una excepción fue lanzada como resultado de mi intento, mediante código, de leer el valor entero del campo de tipo TIntegerField que yo habia creado en la primera posición del array de campos de mi TDataSet. Desde el momento en el que yo había creado el TDataSet y la ejecución de mi código, el campo integer habia sido movido de posición.

Lo que habia sucedio no era mágico. Los campos no pudieron cambiar la posición por ellos mismos, ni lo hicieron en algo basado en mi código. Lo que causó que los TFields cambiaran físicamente su posición en el TDataSet fue que el usuario había cambiado el orden de las columnas en el TDBGrid, el cual estaba vinculados al TClientDataSet (a través del componente TDataSource, por supuesto). La habilidad del usuario para cambiar de posición las columnas en un TDBGrid, por cierto, es el comportamiento por defecto en el componente.

Además de ser interesante (He de suponer que una vez que se abrió conjunto de datos, la posición de la “TFields” en la matriz de Campos ya estaba fijada), este comportamiento es la fuente potencial de excepciones intermitentes, el tipo de las que son particularmente difíciles de localizar. Resulta que este comportamiento, que nunca he visto descrito antes, ha existido desde Delphi 1. (En realidad, he observado este efecto en Delphi 7, Delphi 2007 y Delphi 2010. Sin embargo, entiendo que la fuente subyacente de este comportamiento ha existido desde Delphi 1, aunque no lo he confirmado expresamente.)

He creado una aplicación muy sencilla para demostrar este efecto. Consiste en un único formulario en el que existe un TDBGrid, un TClientDataSet y un componente TButton. El ClientDataSet esta enlazado al TDBGrid a traves del TDataSource. En el evento OnCreate del formulario aparece algo como lo siguiente:

procedure TForm1.FormCreate(Sender: TObject);
begin
  with ClientDataSet1.FieldDefs do
  begin
    Clear;
    Add('StartOfWeek', ftDate);
    Add('Label', ftString, 30);
    Add('Count', ftInteger);
    Add('Active', ftBoolean);
  end;
  ClientDataSet1.CreateDataSet;
end;

Button1, está marcado con la etiqueta “Show ClientDataSetStructure” y contiene el siguiente código en el evento OnClick.

procedure TForm1.Button1Click(Sender: TObject);

var
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    sl.Add('The Structure of ' + ClientDataSet1.Name);
    sl.Add('- - - - - - - - - - - - - - - - - ');
    for i := 0 to ClientDataSet1.FieldCount - 1 do
    sl.Add(ClientDataSet1.Fields[i].FieldName);
    ShowMessage(sl.Text);
  finally
    sl.Free;
  end;
end;

Para demostrar el movimiento de los campos, ejecuta la aplicación, haciendo click sobre el botón marcado con la etiqueta Show ClienteDataSet Structure. Tu debería ver lago semejante a lo que muestra la figura 1.

Figure 1

Luego, arrasta algunas columnas del DBGrid y cambia el orden de los campos. Vuelve a hacer click en el botón “Show ClientDataSet Structure”. En ese momento veras algo similar a lo mostrado en la Figura 2.

Figure 2

Lo remarcable de este ejemplo es que la posicion de los TFields en el propiedad Fields del TClientDataSet cambió, de forma que el campo que estaba  en la posición ClientDataSet.Field[0] en un momento determinado no necesariamente está en un momento posterior. Y desafortunadamente, esto no es responsabilidad del TClientDataSet. He realizado la misma prueba con TTables basadas en el bde y TAdoTables basadas en Ado y se obtuvo el mismo efecto.

Contribuyen al resultado de este comportamiento tres factores. Esto son:

  • Un TDBGrid conectado a un DataSet a través de un DataSource
  • El TDBGrid permite al usuario mover las columnas en tiempo de ejcución
  • Las columnas del TDBGrid son dinámicas; significa esto que son creadas por el TDBGrid en ejecución.

Si tú mediante código haces referencia a los campos del DataSet conectados al TDBGrid, y existen las tres condiciones precedentes usando un indice, tu aplicación puede lanzar una excepción, o producir resultados incorrectos, si el usuario mueve una o mas columnas en ese TDBGrid. En la siguiente sección, considerare algunas soluciones para resolver este problema, asi como compartir con Ustedes la razón de ello.

Existen varias soluciones

Existen varias tácticas que puedes usar para eliminar este potencial bug de vuestras aplicaciones. La primera es definir la TColumns de tu TDBGrid usando campos persistentes.

Crear columnas persistentes, puede ser hecho tanto en tiempo de diseño como de ejecución. Para hacerlo en tiempo de diseño, basta añadir las TColumns usando el editor de Columnas. Éste, se muestra haciendo click con el boton derecho del ratón sobre el TDBGrid y seleccionando el Editor de Columnas o bien haciendo click en la ellipsis de la propiedad Columns del TDBGrid en el Inspector de Objetos. Si tu DataSet está Activo, tú puedes hacer click en el botón “Add All Fields” en la toolbar del Editor de columnas.  O bien, añadir uno o mas TColumns y fijar la propiedad FieldName en el Editot de propiedades.

Para crear columnas en tiempo de ejecución, puedes usar los métodos Add o Create de la propiedad Columns del TDBGrid. Puedes fijar los valores de propiedades especificas de las columnas añadidas o creadas.

La segunda solucion,  aunque tiene algunas consecuencias negativas, previene que el usuario mueva las TColumns del TDBGrid. Esto puede ser hecho eliminando el flag dgResizeColumn de la popriedad Options del TDBGrid. Mientras este enfoque es efectivo, elimina opciones del interfaz potencialmente valiosas. Además, eliminando el flag no solo se restringe la opción de reordenar las columnas sino que impide redimensionar el ancho de las columnas. (Para aprender como limitar reordenar las columnas sin eliminar la opción de cambiar el tamaño de la columna ver el articulo de Zarko Gajic How to allow column resize by disable movement (in TDBGrid).

Una tercera solución es evitar referenciar a los TFields del TDataSet basandonos en un índice literal en la propiedad de tipo array Fields( ya que esta es la esencia del problema). En otras palabras, si tu necesitas acceder al campo Count, definido en el codigo de ejemplo que precede, no uses ClientDataSet1.Fields[2].  En la medida que tu conozcas el nombre del campo, puedes usar algo como ClientDataSet1.FieldByName(‘Count’).

Sin embargo existe una gran desventaja en el uso de FieldByName. En concreto, este metodo identifica el campo iterando a traves de la propiedad Fields del TDataSet, buscando  una concidencia basad en el nombre del campo. Desde el momento en que hace esto cada vez que se invoca FieldByName, deberias evitarlo en situaciones donde el campo necesita ser referenciado muchas veces, como sucede en un bucle que recorre un TDataSet muy extenso, con muchos campos.

Si tu necesitas apuntar al campo repetidamente (y en un gran numero de veces) considera el uso de algo como el siguiente fragmento de código:

var
CountField: TIntegerField;
Sum: Integer;
begin
 Sum := 0;
  CountField := TIntegerField(ClientDataSet1.FieldByName('Count'));
  ClientDataSet1.DisableControls; //assuming we're attached to a DBGrid
  try
    ClientDataSet1.First;
    while not ClientDataSet1.EOF do
    begin
      Sum := Sum + CountField.AsInteger;
      ClientDataSet1.Next;
    end;
  finally
    ClientDataSet1.EnableControls;
  end;
end;

La cuarta solucion es el uso del método FieldByNumber de la propiedad Fields del TDataSet. Si ya tienes código escrito que usa un indice para el array Fields, y trabaja correctamente, siempre y cuando el usuario no mueva las columnas del TDBGrid, esta es otra solucion. Cambia tu codigo para usar FieldByNumber.

Hay dos aspectos interesantes para el uso de FieldByNumber. Primero tu debes cualificar su referencia con la propiedad Fields de tu DataSet. Segundo, al contrario del array Fields, que es basado en indice cero, FieldByNumber  se inicia en 1 para indicar la posición del campo que tu quieres referenciar.

La siguiente es una versión actualizada del manejador del evento OnClick de Button1, mostrado anteriormente, que usa el método FieldByNumber.

procedure TForm1.Button1Click(Sender: TObject);

var
  sl: TStringList;
  i: Integer;
begin
  sl := TStringList.Create;
  try
    sl.Add('The Structure of ' + ClientDataSet1.Name +
       ' using FieldByNumber');
    sl.Add('- - - - - - - - - - - - - - - - - ');
    for i := 0 to ClientDataSet1.FieldCount - 1 do
    sl.Add(ClientDataSet1.Fields.FieldByNumber(i + 1).FieldName);
    ShowMessage(sl.Text);
  finally
    sl.Free;
  end;
end;

Para el ejemplo propuesto, el código produce el siguiente resultado, sin considerar la posición de las columnas en la rejilla asociada. Esto se puede ver en la Figura 3

Figura 3

Hay una quinta solución, pero solo está disponible cuando tu TDataSet es de la clase TClientDataSet, como el que existe en mi ejemplo. En esas situaciones, tu puedes crear un clon del TClientDataSet original, y mantener la estructura original. En consecuencia, cualquiera que sea el campo que originalmente aparezca en la posicion zero, seguira estando en la misma posición, con independencia de que haya podido hacer el usuario al TDBGrid que muestra los datos del TClientDataSet.

Notar que no estoy sugiriendo que  debas referenciar los campos TFields del TDataSet usando literales enteros. Personalmente, el uso de un variable de tipo TField, que se inicializa a través de una llamada a FieldByName es más legible e inmune a los cambios en el orden físico de la estructura de la tabla(¡aunque no sea inmune a los cambios en los nombres de tus campos!) .

Para terminar

Hay un par de puntos finales que quisiera hacer. Primero, la actual estructura subyacente no es afectada. Especificamente, si despues de cambiar el orden de las TColumns en un TDBGrid, llamas la método SaveToFile de la clase TClientDataSet enlazado a ese TDBGrid, la estructura guardada es la original (la verdadera estructura interna). De forma similar, si tu asignas la propiedad Data de un TClientDataSet a otro, el TClientDataSet destino tambien muestra la estructura verdadera (lo cual es similar al efecto observado cuando un origen de datos TClientDataSet es clonado).

De igual forma, cambios en el orden de las columnas de TDBGrids enlazados a otros probados TDataSets, incluyndo TTable y ADOTable, no afectna a la estructura interna de las tablas. Por ejemplo, un TTable que muestra datos desde la tabla de ejemplo de Paradox customer.db que viene con Delphi no cambia esta estructura de la tabla en disco (ni lo esperarias).

El segundo punto es que esto no es un bug en cualquiera de la clases TDataSet o TDBGrid (o TColumn o TField). Es asi como esas clases han sido diseñadas para trabajar. Y aunque este comportamiento introduce errores en tus aplicaciones, esto es porque nosotros no hemos cuidado ésto hasta el momento. Y, tu ahora conoces como se comportan tan bien como para prevenir que causen excepciones en tus aplicaciones con Delphi.

El punto final viene a nosotros por el usuario Sertac Akyuz desde StackOverflow, quien respondió a la pregunta acerca de este comportamiento que yo publiqué en ese sitio Web. Yo había revisado las fuentes tanto para la clase TDataSet como para la clase TDBGrid y no pude localizar el origen del mismo. Sertac escribió que este comportamiento es escontrado actualmente en las clases TColumns y TFields. Especificamente, cuando cambia la posicion de la columna de una TColumn dinámica (no persistente), fruto de una llamada al metodo que fija la propiedad Index de TField, lo cual afecta a la posición de ese campo en la propiedad Fields del TDataSet.

Ahora tu sabes que este potencial problema existe, bajo que condiciones puede emerger, también como sus efectos, deberias ahora echar un vistazo a tus aplicaciones para ver si tienes TDBGris con TColumns no persistentes que el usuario pueda mover en tiempo de ejecución. Si además, referencias las campos TFields con esas TColumns usando indices literales a la propiedad Fields del TDataSet, puedes eliminar errores potenciales resultado de apuntar a un campo erroneo en tiempo de ejecución usando una de las soluciones que yo he apuntando anteriormente en este artículo.

Copyright (C) 2010 Cary Jensen. All Rights Reserved