01 octubre 2007

La potencia de los ClientDataSet (IV)

Ya tenemos todo lo necesario para realizar el programa:

- La base de datos firebird: BASEDATOS.FDB

- La capa de acceso a datos en el módulo de datos: AccesoDatos.

- La lógica de negocio en el módulo de datos: LogicaNegocio.

CREANDO EL FICHERO GENERAL DE CLIENTES

El primer formulario que vamos a crear se va a llamar FClientes y va a contener el listado general de clientes:


Va a contener los siguientes componentes:

- 4 botones de la clase TButton para dar de alta clientes, modificarlos y eliminarlos. Habrá otro llamado BCompletar que utilizaremos más adelante para dar de alta clientes de forma masiva.

- Una rejilla de datos de la clase TDBGrid que va a contener 3 columnas para el listado del cliente: ID, NOMBRE y NIF. Su nombre va a ser ListadoClientes.

- Un componente DataSource (pestaña Data Access) llamado DSLstClientes que va a encargarse de suministrar datos a la rejilla.

- Un componente DBNavitator (pestaña Data Controls) para hacer pruebas con los registros. Lo llamaremos Navegador.

- Una barra de progreso a la izquierda del DBNavigator de la clase TProgressBar que mostrará el progreso de los clientes dados de alta automáticamente a través del botón BCompletar.

Ahora hay que vincular los componentes como corresponde:

- Lo primero es añadir en la sección uses el módulo de datos LogicaNegocio para poder vincular el listado de clientes a la rejilla.

- Vinculamos el componente DSLstClientes con el ClientDataSet llamado TLstClientes a través de su propiedad DataSet.

- En la propiedad DataSource de la rejilla de datos ListadoClientes asignamos el componente DSLstClientes.

- Vinculamos el componente Navegador al componente DSLstClientes mediante su propiedad DataSource.

Más adelante escribiremos código para cada uno de los botones.

CREANDO EL FORMULARIO DEL CLIENTE

Vamos a crear el siguiente formulario llamado FCliente:


Va a constar de tantos componentes de la clase TLabel y TDBEdit como campos tenga la tabla clientes. Todos los campos son modificables a excepción del ID que lo he puesto con su propiedad Enabled a False. También lo he oscurecido podiendo de Color el valor clBtnFace.

Para vincular los campos a la tabla real de clientes introducimos un componente DataSource llamado DSClientes, pero antes hay que añadir en la sección uses el módulo de datos LogicaNegocio.

Ahora se vincula el componente DSClientes con el ClientDataSet llamado TClientes situado en el módulo de datos LogicaNegocio a través de su propiedad Dataset. Con esto ya podemos vincular cada campo TDBEdit con el DataSource DSClientes y con su campo correspondiente especificado en la propiedad DataField.

Cuando se pulse el botón Aceptar ejecutamos el código:

procedure TFCliente.BAceptarClick( Sender: TObject );
begin
LogicaNegocio.TClientes.Post;
ModalResult := mrOk;
end;

Al botón BAceptar le ponemos en su propiedad ModalResult el valor mrNone. Esto tiene su explicación. Cuando pulsemos Aceptar, si el usuario no ha rellenado correctamente algún dato saltará un error definido en los Constraint y no hará nada, evitamos que se cierre el formulario. Así obligamos al usuario a introducir los datos correctamente. Sólo cuando el Post se realice correctamente le diremos al formulario que el resultado es mrOk para que pueda cerrarse.

En el botón BCancelar con sólo asignar en su propiedad ModalResult el valor mrCancel no será necesario hacer nada más.

Sólo una cosa más: si utilizais el teclado numérico en los campos de tipo Float vereis que la tecla decimal no funciona (aquí en España). Nos obliga a utilizar la coma que está encima de la barra de espacio. Para transformar el punto en coma yo lo que suelo hacer es lo siguiente (para el campo IMPORTEPTE):

procedure TFCliente.IMPORTEPTEKeyPress( Sender: TObject; var Key: Char );
begin
if key = '.' then
key := ',';
end;

Es decir, en el evento OnKeyPress cuando el usuario pulse el punto lo transformo en una coma. Si tuvieramos más campos float sólo habría que reasignar el mismo eventos al resto de campos.

Con esto ya tenemos terminado el formulario del cliente.

TERMINANDO EL FICHERO GENERAL DE CLIENTES

Una vez que tenemos la ficha del cliente vamos a terminar el mantenimiento de clientes asignado el código a cada botón. Comencemos con el botón Nuevo:

procedure TFClientes.BNuevoClick( Sender: TObject );
begin
LogicaNegocio.TClientes.Open;
LogicaNegocio.TClientes.Insert;
Application.CreateForm( TFCliente, FCliente );
FCliente.ShowModal;
LogicaNegocio.TClientes.Close;
end;

Como puede apreciarse abrimos la tabla TClientes sólo cuando hay que dar de alta un cliente o modificarlo. Después la cerramos ya que para ver el listado de clientes tenemos nuestra tabla TLstClientes.

Para el botón Modificar es muy parecido:

procedure TFClientes.BModificarClick( Sender: TObject );
begin
LogicaNegocio.TClientes.Params.ParamByName( 'ID' ).AsString := LogicaNegocio.TLstClientesID.AsString;
LogicaNegocio.TClientes.Open;
LogicaNegocio.TClientes.Edit;
Application.CreateForm( TFCliente, FCliente );
FCliente.ShowModal;
LogicaNegocio.TClientes.Close;
ListadoClientes.SetFocus;
end;

Aquí hay que detenerse para hablar de algo importante. Cuando abrimos una tabla por primera vez el cursor SQL dentro del motor de bases de datos se va al primer registro. ¿Cómo hacemos para ir al registro seleccionado por TLstClientes? Pues le tenemos que pasar el ID del cliente que queremos editar.

Esto se hace utilizando parámetros, los cuales hay que definirlos dentro del componente ClientDataSet llamado TClientes que está en el módulo de datos LogicaNegocio. Generalmente cuando se pulsa CTRL + A para traernos los campos de la tabla al ClientDataSet se dan de alta los parámetros. Como el componente TClientes está vinculado al IBQuery Clientes que tiene la SQL:

SELECT * FROM CLIENTES
WHERE ID=:ID

entonces al pulsar CTRL + A en el ClientDataSet nos da de alta automáticamente el parámetro ID. Si no fuera así, tendríamos que ir al componente TClientes, pulsar el botón [...] en su propiedad Params y dar de alta un parámetro con las propiedades:

DataType: ftInteger
Name: ID
ParamType: ptInput

Los parámetros dan mucha velocidad a un programa porque permiten modificar las opciones de la SQL sin tener que cerrar y abrir de nuevo la consulta, permitiendo saltar de un registro a otro dentro de una misma tabla a una velocidad impresionante.

Y por último introducimos el código correspondiente al botón Eliminar:

procedure TFClientes.BEliminarClick( Sender: TObject );
begin
with LogicaNegocio.TLstClientes do
if RecordCount > 0 then
if Application.MessageBox( '¿Desea eliminar este cliente?', 'Atención',
MB_ICONQUESTION or MB_YESNO ) = ID_YES then
Delete;

ListadoClientes.SetFocus;
end;

Antes de eliminar el registro nos aseguramos que de tenga algún dato y preguntamos al usuario si esta seguro de eliminarlo.

Con esto finalizamos nuestro mantenimiento de clientes a falta de utilizar el botón Completar donde crearemos un bucle que dará de alta tantos clientes como deseemos para probar la velocidad de la base de datos. Esto lo haremos en el próximo artículo.

Pruebas realizadas en Firebird 2.0 y Delphi 7.

24 comentarios:

Unknown dijo...

Estos tutoriales están genial pero tengo una duda...
Las actualizaciones de los clientdataset no me aparecen en la base de datos hasta que cierro la aplicación. ¿Cuándo se hace commit en la transacción?¿En qué capa debemos forzarlo? porque aparentemente el updaterecord del ibquery se hace antes que el del clientdataset, y no veo que en el clientdataset se deba hacer referencia a un commit si queremos separar la aplicación en capas

Unknown dijo...

Estos tutoriales están genial pero tengo una duda...
Las actualizaciones de los clientdataset no me aparecen en la base de datos hasta que cierro la aplicación. ¿Cuándo se hace commit en la transacción?¿En qué capa debemos forzarlo? porque aparentemente el updaterecord del ibquery se hace antes que el del clientdataset, y no veo que en el clientdataset se deba hacer referencia a un commit si queremos separar la aplicación en capas

Administrador dijo...

Eso ocurre porque la trasacción no la ha enviado al servidor hasta que te sales del programa.

Para solucionar esto tienes que modificar el evento AfterPost del objeto ClientDataSet y llamar al comando ApplyUpdates( 0 ).

Por ejemplo, si el objeto ClienteDataSet se llama TArticulos tienes que hacer lo siguiente en su evento AfterPost:

if TArticulos.ChangeCount > 0 then
TArticulos.ApplyUpdate( 0 );

Ese comando enviará la trasacción al servidor y se refrescarán las tablas de la base de datos sin salir del programa.

ok?

Unknown dijo...

Sí, tengo escrito ese código en el afterpost del ClientDataSet, aún así no se realiza la actualización hasta salir. Abra otra instancia o verifique desde IBExpert, los datos no aparecen actualizados.

Trabajo con Delphi 7 + Firebird 2 con componentes IBX.

¿Puede ser que no tenga bien configurada la Transacción (mal parametrizada)?

Administrador dijo...

A ver si va a ser porque estas utilizando la misma transacción que la del objeto IBDatabase.

Crea un objeto IBTransaction para esa tabla que estás guardando (y asocialo a la misma).

A veces cuando utilizamos muchas tablas asociadas a la misma transacción, no se actualizan las tablas hasta que todas han hecho Post. Si alguna tabla deja abierta alguna consula SQL entonces no se puede enviar toda la transacción al motor de bases de datos.

Y si no te funciona, seguiremos pensando. No de desanimes.

Saludos

Anónimo dijo...

que mas taxilom, lo felicito por los tutoriales estan muy buenos, que gran aporte, pero tengo una duda, a mi me pasa lo mismo que a reasons solo me actualiza en la bd cuando cierro mi aplicacion, si intento hacer lo que ud dice del applyupdates me dice que no se puede hacer esta operacion con el dataset cerrado, entonces abro el dataset, clientdataset.open y entonces me bota otro error mismatch in datapacket, agradeceria me pudiera guiar o dar alguna pauta para solucionar este problema, muchas gracias y de nuevo felicitaciones por el tutorial esta muy bueno

Administrador dijo...

Mucho cuidado si estas utilizando Delphi 7 porque ese error se debe a que la versión de los componentes IBX que trae esa versión de Delphi está defectuosa.

Hay que actualizar los IBX a una versión superior a la que lleva Delphi 7.

Creo que ese es el problema que tienes.

Saludos.

Anónimo dijo...

Buen dia eh estado mirando el tutorial y siguiendo algunos de los consejos que le diste a los compañeros, tenia el mismo problema con los componentes ibx, los actualice y continua todo igual no puedo poner a active el clientdataset, no se que estare haciendo mal, el codigo sql que quiero ejecutar en el ibquery que enlazo con el clientdataset es el siguiente
delete from profesores
where nombreProf=:ProfesorAEliminar
y me borra el campo correspondiente de la tabla agregando al evento de borrar el siguiente codigo
form1.ClientDataSet2.Active:=false;
form1.ClientDataSet2.Params[0].AsString:=form1.Edit2.Text;
form1.ClientDataSet2.Execute;

Pero solamente lo borra cuando cierro mi aplicacion, intento poner como indicas aca el metodo applyupdates en el evento afterpost y me genera un error, ne dice que no se puede realizar esta operacion sobre un clientdataset cerrado e intento abrirlo y me genera otro error.
Te agradeceria si pudieras orientarme sobre que error puedo estar cometiendo.

Administrador dijo...

Cuando ejecutas alguna sentencia (insertar, modificar o eliminar) sobre un registro y no se entera hasta que cierras la aplicación es porque no has mandado la transacción al servidor.

Como dije en el artículo hay que poner en el método OnAfterPost:

if ChangeCount > 0 then
ApplyUpdates( 0 );

Y asegúrate de que la transacción esta bien enlazada al TIBQuery y a la base de datos.

Ahora bien, para averiguar que error te está dando en el evento OnAfterPost tienes que escribir este código en el evento OnReconcileError del ClientDataSet:

showmessage( e.message );

De este modo te dirá que problema ha ocurrido al enviar la transacción al servidor.

Mucha suerte y si vuelves a tener problemas me lo dices y ya pensaremos otra cosa.

Saludos.

Anónimo dijo...

Muchas gracias por tu ayuda, oye el codigo lo tengo asi como indicas en el metodo afterpost del clientdataset tengo el codigo que indicas
if ChangeCount > 0 then
ApplyUpdates( 0 );
y tambien hice la prueba que me indicas en el evento onreconcileerror del clientdataset el siguiente codigo
showmessage(e.message);
pero no se me genera ningun mensaje
asi mismo eh comprobado que las conexiones esten correctas y todo esta bien, tanto en el transaction como en el ibquery como en el ibdatabase, la verdad no se que pueda estar pasando y ya tengo varios dias intentando solucionar esto, agradezo demasiado tu atencion y colaboracion.

Anónimo dijo...

hola compañeros taxilom administrador reasons y anonimo2, yo soy anonimo1, he seguido atentamente todas las indicaciones que aqui se han dado pero sigo con el mismo problema, ni siquiera puedo hacer el evento clientdatset.post, ya que cuando quiero hacer esto me dice que el dataset debe estar en modo insert, cuando le pongo el insert me dice que el cds debe estar abierto y le pongo el open y me sale el mensaje de mismatch in datapacket, este mismo mensaje me sale cuando intento poner el cds en active:=true, no se si sea algun error en la sentencia sql, o si el ibquery deba tener algun parametro especial o algo asi, la verdad no se me ocurre nada mas, la consulta sql es la siguiente

delete campo1 from tabla where campo2=:parametro

tambien cabe aclarar que los ibquery's de consulta o los ibtables sino prsentan ningun problema, solo son los de modificar y eliminar,, muchas gracias por la ayuda que me puedan brindar de antemano estoy muy agradecido por la atencio y la ayuda gracias y chao

Anónimo dijo...

Hola, antes que nada los felicito por este exelente blog y por su gran ayuda, ahora paso a mi consulta.

Trabajo con clientdataset y tengo un manejado de errores en el evento onreconcileerror, el tema es que estoy usando componentes dbadware y cuando sucede un error en el evento antes descripto no y la accion predeterminada es correct no puedo seguir la edicion de los campos en los controles, o sea, por ejemplo:

campo 1 valor 12345
campo 2 valor (null) requerido

aqui da un error porque el campo 2 es requerido, pero al salir el error no puedo agregar el valor al campo 2 y pierdo lo que cargue en el campo 1, imaginense si eran 20 campos?, que estoy haciendo mal!, muchas gracias.

Administrador dijo...

Puede ser que el campo requerido sea autogenerado por la tabla pero el objeto ClientDataSet no se ha enterado.

Eso me pasa a mi con Interbase cuando utilizo campos ID que se rellenan automáticamente con un generador y un disparador (TRIGGER).

Lo que hago es decirle al objeto ClientDataSet y al de su provider (por ejemplo IBTable o IBQuery) que ningún campo es requerido.

¿Podría ser ese tu caso?

Unknown dijo...

Hola,

agradezco la ayuda que nos das en tu blog.... tengo el siguiente error, cuando intento editar un registro..

...Record not found or changed by another user'.....


...Unable to find record. No key specified'.....

gracias por la ayuda

Administrador dijo...

Eso tiene pinta de que has creado una tabla sin un campo que haga de clave primaria (ID).

Por ejemplo:

CREATE TABLE CLIENTES
(
ID INTEGER NOT NULL,
NOMBRE VARCHAR(50),
NIF VARCHAR(15),
PRIMARY KEY(ID),
)

Revisa la estructuras de tus tablas para que luego los componentes IBQuery y ClientDataset puedan averiguar quien es la clave primaria.

Saludos.

Anónimo dijo...

Tus tutoriales son fabulosos...
No puedo creer lo SENCILLO y bien explicado que esta todo.... GRACIAS

Max Demian dijo...

Espero que el comentario le sirva a alguien, ya que hace mucho que se inicio este thread. Quería aclarar que un problema que tienen algunos con la confirmación de las transacciones se debe a que confunden ApplyUpdate de los TClientDatasets con Applyupdates() de la base de datos. Este ultimo es completo: para todas las tablas del conjunto que se le pasa como parámetro llama a ApplyUpdates y si todo estuvo bien llama a CommitUpdates (este último método de los clientdatasets confirma definitivamente los cambios). Es conveniente llamar al Applyupdates del objeto database, pero si se llama al de los clientdatasets también se debe llamar a CommitUpdates

Anónimo dijo...

Arriba dices:

Sólo una cosa más: si utilizais el teclado numérico en los campos de tipo Float vereis que la tecla decimal no funciona (aquí en España). Nos obliga a utilizar la coma que está encima de la barra de espacio. Para transformar el punto en coma yo lo que suelo hacer es lo siguiente (para el campo IMPORTEPTE):


procedure TFCliente.IMPORTEPTEKeyPress( Sender: TObject; var Key: Char );
begin
if key = '.' then
key := ',';
end;

Yo lo que hago es poner en el Form principal lo siguiente:

procedure TForm_FormMain.Form_OnCreate(Sender: TObject);
begin
{
al crear la ventana, definir los parámetros "por defecto" del sistema
}
DateSeparator := '/';
DecimalSeparator := '.';
LongDateFormat := 'dd/mm/yyyy';
ThousandSeparator := ',';
TimeSeparator := ':';
{
posicionar la ventana
}
...

Administrador dijo...

Gracias por la información. Con esto también nos ahorramos código.

MarlonVillamizar dijo...

Buenas noches, tengo una duda acerca de los ClientDataSet, si deseo modificar en tiempo de ejecución el argumento de la propiedad SQL del ibquery como puedo hacerlo si no tengo acceso al Ibquery. Gracias... es la unica duda que tengo, les comento que con respecto al turorial está muy claro solo que el problema de la actualización lo solucione configurando las transaccines de esa manera a mi me funciona todo muy bien. Gracias...

Administrador dijo...

La única forma de cambiar la SQL es accediendo al objeto que está enganchado al provider (por ejemplo TIBQuery).

Ten en cuenta que el objeto ClientDataSet es una memoria caché de la SQL original. De modo que para modificar la SQL en tiempo real, debe cerrar el objeto ClientDataSet, modificar el TIBQuery y luego abrilo de nuevo.

A menos que utilices parámetros. Lo que pasa es que no son muy flexibles.

Saludos.

MarlonVillamizar dijo...

Bueno, Muchas gracias por la respuesta.
Ya logré hacerlo de la siguiente manera:
1.- en el DatSetProvider.ppoAllowCommandText := True;
y dinamicamente;
LogicaNegocio.CDSPacxCedula.Close;
LogicaNegocio.CDSPacxCedula.CommandText := 'select * from pacientes where apellidos like :apellidos order by apellidos asc';
LogicaNegocio.CDSPacxCedula.Params.ParamByName('apellidos').AsString := Trim( EdtBuscar.Text )+'%';
LogicaNegocio.CDSPacxCedula.Open;

¡¡¡ Listo, funcionó... !!! aprovecho la ocación para saludor al administrador y felicitarlo porque por medio de este blog he aprendido y me siento satisfecho... mil gracias.

Unknown dijo...

Buenos dias segui todo tu ejemplo y cuando corro el programa al dar clic sobre aceptar se bloque en:

procedure TLogicaNegocio.TClientesAfterPost(DataSet: TDataSet);
begin
if TClientes.ChangeCount > 0 then
begin
TClientes.ApplyUpdates( 0 );

/*****aqui
TClientes.Refresh;
/*****aqui
if TLstClientes.Active then
TLstClientes.Refresh;
end;

y genera este error Project base1.exe raised exception class EIBClientError whit message 'SQL Parse Error: Parameter name expected'...........

no se que pueda ser, te agradezco cualquier ayuda

Unknown dijo...

Hola muy bueno tu blog, tengo una duda como seria el procedimiento para actualizar 2 tablas por ejemplo PEDIDO y DETALLE PEDIDO

Publicidad