El problema tiene que ver con la forma en que trabajan los métodos SetData, Validate y GetData de la clase base nativa TField.
Primero, el método SetData establece en el campo (
variable)
FValueBuffer el valor que está por ser asignado al campo:
Código Delphi
[-]procedure TField.SetData(Buffer: Pointer; NativeFormat: Boolean = True);
begin
if FDataSet = nil then DatabaseErrorFmt(SDataSetMissing, [DisplayName]);
FValueBuffer := Buffer;
try
FDataSet.SetFieldData(Self, Buffer, NativeFormat);
finally
FValueBuffer := nil;
end;
end;
Luego el método SetFieldData del conjunto de datos (sea BDE, ADO, etc.), llama al método Validate del campo:
Código Delphi
[-]procedure TBDEDataSet.SetFieldData(Field: TField; Buffer: Pointer);
var
RecBuf: PChar;
Blank: LongBool;
begin
with Field do
begin
if not (State in dsWriteModes) then DatabaseError(SNotEditing, Self);
if (State = dsSetKey) and ((FieldNo < 0) or (FIndexFieldCount > 0) and
not IsIndexField) then DatabaseErrorFmt(SNotIndexField, [DisplayName]);
GetActiveRecBuf(RecBuf);
if FieldNo > 0 then
begin
if State = dsCalcFields then DatabaseError(SNotEditing, Self);
if ReadOnly and not (State in [dsSetKey, dsFilter]) then
DatabaseErrorFmt(SFieldReadOnly, [DisplayName]);
Validate(Buffer);
...
El método TField.Validate es quien se encarga de llamar al evento OnValidate, estableciendo temporalmente una bandera,
FValidating, en True:
Código Delphi
[-]procedure TField.Validate(Buffer: Pointer);
begin
if Assigned(OnValidate) then
begin
if FValueBuffer = nil then
FValueBuffer := Buffer;
FValidating := True;
try
OnValidate(Self);
finally
FValidating := False;
end;
end;
end;
Dicha bandera tiene efecto en la forma en que trabaja el método TField.GetData, el cual se ejecuta siempre que algo requiere leer el valor del campo.
Código Delphi
[-]function TField.GetData(Buffer: Pointer; NativeFormat: Boolean = True): Boolean;
begin
if FDataSet = nil then DatabaseErrorFmt(SDataSetMissing, [DisplayName]);
if FValidating then
begin
Result := LongBool(FValueBuffer);
if Result and (Buffer <> nil) then
CopyData(FValueBuffer, Buffer);
end else
Result := FDataSet.GetFieldData(Self, Buffer, NativeFormat);
end;
Como puede verse, cuando no se está ejecutando el método Validate (FValidating es False), el valor del campo es obtenido a través del método GetFieldData del conjunto de datos, el cual traerá el valor real del campo que corresponda a la fila actual.
Pero si la bandera FValidating es True, el valor NO será leído del registro actual, sino del buffer FValueBuffer establecido desde que se llamó a TField.SetData (cuando la captura del dato fue recién ingresada).
El problema con este mecanismo ya tan antiguo de la VCL es que si, durante la ejecución del evento OnValidate, algo necesita leer el valor del campo en cuestión pero de diferentes filas —tal como ocurre siempre en un control TDBGrid cuando éste se despliega—, se estará leyendo y dibujando en pantalla el mismo valor para todos los registros.
Cuando la validación termina, el método Validate vuelve a poner la bandera FValidating en False, permitiendo que, la siguiente vez que la rejilla se dibuje, el método GetData lea el valor del registro real y ya no del buffer temporal FValueBuffer.
Saludos.
Al González.
