Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

Acción inmediata

Había liquidado los últimos fallos de aquel monstruoso programa y lo estaba mostrando, lleno de orgullo, a uno de sus futuros usuarios. Este dedicaba su atención a una ventana mediante la cual podía contratar determinados servicios de la compañía. El registro del producto contenía un campo lógico, que al ser activado debía añadir un seguro mensual al contrato; el coste del seguro debía reflejarse en el importe total de la contratación. Naturalmente, la edición de dicho campo se realizaba mediante un componente TDBCheckBox.

Pepe, llamémosle así piadosamente, decidido a hacer estallar mi aplicación, seleccionó precisamente la casilla mencionada, después de bregar fatigosamente con el insumiso ratón. Pulsó sobre el control y me miró con insolencia: "Oye, esto no calcula el seguro". Contuve mis deseos de arrancarle la lengua y arrojarla a la perra del portero, y me concentré en el monitor ... ¡oops! ... ahí pasaba algo muy raro. Efectivamente, la opción estaba marcada, ¡pero el importe seguía siendo el mismo! Le arrebaté el ratón y cambié el estado del control unas cuantas veces, pero aquello seguía más tieso que la momia de Lenin. Tras unos insoportables segundos de sudores fríos, comprendí lo que pasaba. "Pepe, macho" - le dije, dándole a su nombre la misma entonación que la usual en el adjetivo 'gilipollas' - "tienes que pulsar la tabulación para pasar al siguiente control". Me miró con sorna, respondiendo: "tú te confundiste también". Y en lo más íntimo de mi encéfalo tuve que reconocer que, por una vez en la vida, aquel especimen de usuario tenía razón...

¿CUANDO SE ACTUALIZA UN CAMPO?

La anécdota ficticia que acabo de relatar (¡ninguno de mis usuarios se llama Pepe!) demuestra un comportamiento anómalo de TDBCheckBox, pero que también es padecido por los restantes controles de datos de Delphi. Digámoslo en pocas palabras:

Los cambios realizados en un control de datos de la VCL tienen lugar,
por lo general, sólo cuando abandonamos el control.

Es decir: nos ponemos a teclear sobre un TDBEdit, pero mientras tecleamos el contenido original del campo asociado sigue siendo el mismo. Tenemos que pasar al siguiente control dentro del formulario para que las modificaciones surtan efecto. En ese momento es que se disparan, además, los eventos OnValidate y OnChange del campo, si es que tienen código asociado.

¿Por quéeeee? Aceptemos lo contrario: que cada vez que cambie el contenido del editor, se modifique el campo simultáneamente. Esto es posible y sensato si el campo editado es numérico, o si es un nombre, por mencionar un par de casos. Pero no es factible cuando el campo representa una fecha; la cadena "12/", a pesar de nuestras mejores intenciones de completarla en breve, no representa una fecha correcta.

Así que la culpa de todo la tiene el control TDBEdit. Es lógico que suceda lo mismo con un TDBComboBox, pues en el fondo se trata simplemente de un TDBEdit con cuernos ... quiero decir, con lista desplegable. Pero no es tan evidente cuando se trata de un TDBCheckBox, o de un TDBLookupComboBox, pues estos controles siempre (o casi siempre) contienen un valor correcto para el campo que representan. Irónicamente, el culpable TDBEdit ofrece un mecanismo adicional para que el usuario diga "lo que he tecleado hasta aquí vale", sin necesidad de pasar al control siguiente. Si tecleamos Intro sobre el control, su texto pasa inmediatamente al campo.

¿COMO SE ACTUALIZA UN CAMPO?

Ya puestos, mostremos el código que realiza la asignación al campo en los componentes mencionados. El siguiente método, por ejemplo, corresponde a un TDBEdit:

procedure TDBEdit.CMExit(var Message: TCMExit);
begin
   try
      FDataLink.UpdateRecord;
   except
      SelectAll;
      SetFocus;
      raise;
   end;
   SetFocused(False);
   CheckCursor;
   DoExit;
end;

La llamada al método UpdateRecord del FDataLink desencadena el proceso de asignación al campo. Esta llamada provoca que el componente de clase TFieldDataLink enganchado en FDataLink envíe una notificación a todos los controles asociados al mismo conjunto de datos, para que todos ellos vuelquen sus contenidos en la fila activa. Los controles escuchan la notificación interceptando el evento interno OnUpdateData del FDataLink. El código correspondiente en el editor es:

constructor TDBEdit.Create(AOwner: TComponent);
begin
   inherited Create(AOwner);
   // ... más instrucciones ...
   FDataLink := TFieldDataLink.Create;
   // ... muchas más instrucciones ...
   FDataLink.OnUpdateData := UpdateData;
end;

procedure TDBEdit.UpdateData(Sender: TObject);
begin
   ValidateEdit;
   FDataLink.Field.Text := Text; // ¡Esto es lo que buscábamos!
end;

UNA SOLUCION PARA LOS CHECK BOXES

Mi solución preferida es crear un componente; cualquier otra implicaría añadir chapuzas para simular el cambio del foco del teclado, al menos hasta donde tengo noticia. En el siguiente componente (un derivado de TCustomCheckBox) que simula parte del comportamiento de TDBCheckBox y añade algunas características adicionales, hay que redefinir el método Toggle, que se ejecuta cada vez que cambia el estado del control:

procedure TimDBCheckBox.Toggle;
begin
   if FDataLink.Edit then
   begin
      inherited Toggle;
      FDataLink.Modified;
      if FImmediate then              // Nueva línea
         FDataLink.UpdateRecord;      // Nueva línea
   end;
end;

Immediate es una propiedad de tipo Boolean, y sirve para activar el modo de asignación inmediata a campos.