Facebook Grupo Delphi Solidario




Sígueme desde Facebook Delphi Solidario
MIEMBROS: 176
MODERADORES: 3

Danysoft: Programa de certificación Delphi

Programa de Certificación Delphi
Danysoft
 

Solidaridad: Fundación Vicente Ferrer

Apadrina un niño

Delphi Básico

Algunos comentarios y reflexiones sobre Delphi
  • Inicio
  • Al día
    • Eventos próximos
    • Desde la trinchera
  • Temas de discusión
  • Comunidad
    • Foros
    • Publicaciones
    • Truco o trato…     
  • Legal
    • Aviso legal
    • Politica de privacidad

You are browsing the Blog for TBrowser.

Un día con los mayores (5) y Parte B

marzo 20, 2010 en Ado Express & DataSnap, Artículos, ¿Sabías que...?, ¿Sabías que...? [Delphi], Código, Delphi, Entrada Diario, framework, Taller práctico

Os pido -antes que nada- disculpas por la ausencia, ya que han tenido que pasar algunas semanas, hasta que he podido retomar esta última parte de la quinta entrega, en la que se abordaba la implementación de la clase TRejilla. Ésta, como otras comentadas anteriormente, forman una muy reducida parte o colección, extraida del framework de los cursos que Ian Marteens ha estado impartiendo en el area de gestión de datos, y concretamente de Datasnap.   Hemos sido muchos los programadores que hemos aprendido con sus cursos, directa o indirectamente, y aunque Ian desde hace tiempo centró su trabajo, a tenor de lo que hemos ido siguiendo en sus blogs, en la esfera de C# y Freya, lo escrito por él sigue siendo válido y aplicable en muchos aspectos, y muchos todavía sentimos agradecimiento hacia sus libros publicados, sus Caras Ocultas, escritos entonces en un contexto en el que existía menos documentación a nuestro alcance. Creo que se entiende que, para mi, iniciar la serie, era una forma de agradecerle esa aportación.

Como sabéis, la iniciabamos meses atras con la única y sana intención de compartir la experiencia de estar manteniendo una aplicación real basada en su framework . Si hacemos un poco de memoria, cerrábamos la parte A, anticipando el interfaz que publicaba la clase y dejábamos para esta entrada comentar la implementación.

No… No es que me sobre el tiempo ahora, :-) pero  cada vez que abría el editor del blog, pendía como una espada de Damocles sobre mi conciencia, y susurraba a mi oído algo similar a:

-”Salvadooooor… estoy esperandote… y van pasando las semanas y sigo estando en la zona de borradores…!!!“

(voz tenebrosa)

=:-)

Tan espectral visión de la pereza humana ha sacudido mi aletargado espíritu…

Fuera ya de bromas, los últimos meses han supuesto una sobrecarga de trabajo bastante grande y a pesar de mi voluntad en mantener al día el contenido del blog y finalizar la serie, (porque siempre hay algo que contar y compartir ¿no creéis?), no siempre nos es posible atenderlo como quisiéramos y merece. Así que he dejado aparcados durante un par de días varios temas pendientes, personales y de mi trabajo, y heme aquí, intentando retomar el hilo de lo escrito ( hilo perdido y bien perdido).

Vamos a ver…

Lo primero que tenemos que tener en cuenta es que la clase TRejilla, nos debería proveer la funcionalidad básica para manipular los datos de forma sencilla. Eso es lo que siempre se ha buscado. Así que la clave, en este punto puede estar en valernos de Acciones, que podrán ser asignadas en tiempo de diseño durante la etapa de creación de los módulos de trabajo. Es decir, que nuestro trabajo consistiría básicamente en decidir a tenor de cada uno de ellos, cuales acciones van a quedar disponibles, en la etapa de diseño, sin tener que estar reinventando la rueda continuamente. Esta sería mas o menos la mecánica de trabajo para nosotros a este nivel, durante el desarrollo de nuestra aplicación: Creamos un módulo descendiente de la clase TBrowser, añadimos un componente TRejilla en su interior y ¡voila!, asignamos las acciones que vamos a permitir en aquellos elementos del interfaz que las requieran. Si a este punto añadimos que Ian, hacia formar parte del browser una ToolBar que puede ser receptor de las acciones, el trabajo se simplifica aun mas.

Recordemos la imagen en la que podía ver que elementos iban a formar parte del componente TRejilla:

TRejilla

Figura 1.

En el interior del componente TActionList, se habían creado las diferentes acciones (TAction) relacionadas directamente con la manipulación de los datos.

Contenido LocalActions

Figura 2.

Las cinco primeras acciones forman parte de las acciones standard predefinidas para manipulación de datos, existentes en el editor de acciones usado en tiempo de diseño . Marteens, se vale de las cinco primeras, primer registro, anterior, siguiente, ultimo y actualizar. El resto, buscar, insertar, borrar y modificar son creadas partiendo de una acción normal, no predefinida.

Esta es una imagen de las acciones predefinidas standard, que abarcan múltiples contextos, uno de ellos entre otros, el que hace referencia a datos.

Acciones predefinidas de tipo standard

Figura3.

La propiedad más importante que tenéis que cumplimentar en este caso (nos referimos a las cinco primeras) es la asignación del datasource de cada acción para que apunte al datasource del browser (dsBase). De esa forma, movernos a través de los registros del dataset no nos costará una sola linea de código, ya que la acción ya implementa la activación de la misma en función de la posición del registro activo y la acción propia del movimiento de navegación. Esto es aplicable a DataSetFirst, DataSetPrior, DataSetNext, DataSetLast, y DataSetRefresh, que veis añadidas en el contenedor LocalActions.

Centrémonos ahora en DataSetSearch, que representa la acción que nos permitirá definir qué registros formarán parte de la rejilla que va a visualizar el usuario de la aplicación. Habitualmente definimos estás acciones como búsquedas y suele ser habitual que nuestros usuarios demanden un gran numero de ellas siguiendo los más pintorescos y variopintos deseos (y necesidades). Aunque vamos a omitir esta parte en el ejemplo práctico que nos propondremos para finalizar la serie, en aras de simplificar el código, sí podemos comentar el razonamiento ingenioso que ha seguido Ian para habilitar la funcionalidad, ya que es muy interesante y didáctico.

RECETA PARA HABILITAR UNAS BÚSQUEDAS DE FORMA SENCILLA:

Una vez creada la acción (DataSetSearch) , necesitamos concretar y definir ese elemento clave y central, sobre el que van a centrar las búsquedas. Ian Marteens llamó a ese elemento TGeneradorSQL, que va a ser, algo tan básico como una función que nos devuelva un string, representando la cadena sql mediante la que obtenemos los resultados de la  petición.

Esta es la declaración del tipo, previa lógicamente a la establecida por TRejilla

TGeneradorSQL = function : string of object;

Asi que definir una búsqueda, se acaba convirtiendo simplemente en dar respuesta en el modulo de navegación a varias de estas funciones.

Yo por ejemplo, en la unidad UActividades, donde el usuario del Centro visualiza que actividades quedan disponibles para suscribirse, escribo unas lineas de código tal que así:

function TActividades.CadenaSQL: String;
begin
  Result:= 'SELECT dbo.Actividades.IDActividad,	' +
           '       dbo.Actividades.CodigoActividad, ' +
           '       dbo.Actividades.Nombre ' +
           'FROM  dbo.Actividades ' +
           'order by CodigoActividad ';
end;

No… :-) Ya suponéis que no me he roto demasiado la cabeza, siquiera para definir los parámetros, sino que en este caso, nos devuelve todas las actividades existentes (que no eran demasiadas) ordenadas por código de actividad.

¿Veis en la figura 1, que existe un popupmenu, que he tachado para indicaros que no iba a ser incluido?. Ese popupmenu respondía al nombre de SearchPopup, y era manipulado mediante código en tiempo de ejecución, para que contuviera cada una de las acciones de búsqueda (definidas previamente en cada consulta sql devueltas por las funciones TGeneradorSQL). Así que bastaba crear en la barra de acciones (toolbar) del browser, un botón nuevo, al que se iba a asignar la ejecución del código de la acción, (que de forma genérica, era capaz de obtener cual de los TMenuItems era el pulsado por el usuario y cargar el dataset con la consulta vinculada al mismo).

Ajá… parece complicado ¿no es así?. Sin embargo, en la práctica es sencillo. Tan sencillo como definir una linea como la siguiente, de código en el evento de creación del formulario Browser:

RejillaActividades.TipoBusqueda('Todos los registros', CadenaSQL, True);
Su ejecución, provocaba que se creara dinámicamente uno de los items del componente TPopupMenu, y estuviera disponible para ser usado por el usuario. En este caso concreto, el que hacía referencia a la función definida lineas más arriba.
procedure TRejilla.TipoBusqueda(const Comando: string;
  Generador: TGeneradorSQL; PorOmision: Boolean);
var
  NewItem: TMenuItem;
begin
  if TMethod(Generador).Data <> Owner then Exit;
  NewItem := TMenuItem.Create(Self);
  NewItem.Caption := Comando;
  NewItem.OnClick := DatasetSearch.OnExecute;
  if PorOmision then
    FUltimaBusqueda := NewItem;
  NewItem.Tag := Integer(TMethod(Generador).Code);
  SearchPopup.Items.Add(NewItem);
end;

Si analizamos el nucleo del código vinculado al evento OnExecute de la acción DataSetSearch,

procedure TRejilla.DataSetSearchExecute(Sender: TObject);
var
  D: TClientDataSet;
  Gen: TGeneradorSQL;
begin
  D := dsBase.DataSet as TClientDataSet;
  if Assigned(D) then
  begin
    ...
    TMethod(Gen).Data := Owner;
    TMethod(Gen).Code := Pointer(FUltimaBusqueda.Tag);
    D.Close;
    D.CommandText := Gen();
    D.Open;
  end;
end;

Ahora ya podemos entender porque se guardaba en el tag del componente TMenuItem, el puntero al punto de entrada de la función (del método), que nos permite de forma “mágica” ejecutar la función asociada (representada por la variable Gen) y asignar la consulta SQL que abrirá el dataset de la rejilla.

Y lo mas bonito de todo esto, es que a nosotros, como programadores nos queda en este esquema de trabajo centrarnos en lo realmente importante, que es definir que consultas va a necesitar el usuario para que sus busquedas sean funcionales y eficaces. Este será el esquema a seguir en este caso: Asignar el boton que va a contener la acción de busqueda en la toolbar. Una vez hecho esto, le asignamos a dicho botón el popupmenu a su propiedad Dropdown, que espera el componente TPopupMenu que debe ser mostrado al expandirse el menú. Simultaneamente, debe tomar el valor Style: tbsDropDown, para que al pulsar sobre el componente TToolButton, permita que se despliegue dicho menú de búsquedas automáticamente y pueda ser pulsado por el usuario (que habría ejecutado finalmente el código del evento OnExecute de la acción DataSetSearch).

Es imposible comentar todos los matices e inquietudes que nos puede abrir la observación de estas lineas, que os aseguro que son bastantes. En mi caso concreto, la primera vez que me enfrenté a un código similar, salvando las diferencias, fue intentando descifrar que hacía entre bastidores la clase TThread (definida en la unidad Classes.pas), allá por Delphi 6. Si haceis memoria, una de las series que acompañó a la Revista Síntesis, fue concretamente una en la que se abordaban los hilos de ejecución en Delphi 5. Meses mas tarde, comentaba en uno de los artículos que el código había sido modificado con la nueva versión (Delphi 6) y que se habían echo algunos cambios significativos. Creo que fue la primera vez que vi código en el que se veía la asignación del doble puntero Code/Data (aunque en aquel caso estaba definido como procedure of object, si no recuerdo mal).

  TMethod = record
    Code, Data: Pointer;
  end;

Una vez, comentada la acción de busqueda, vamos a descifrar el resto de las acciones (DataSetInsert, DataSetUpdate y DataSetDelete):

{ TRejilla: acciones de actualización }

procedure TRejilla.DataSetInsertExecute(Sender: TObject);
var
  D: TClientDataSet;
begin
  D := dsBase.DataSet as TClientDataSet;
  if Assigned(D) then
    ManipuladorDatos.Insertar(D);
end;

procedure TRejilla.DataSetDeleteExecute(Sender: TObject);
var
  D: TClientDataSet;
begin
  D := dsBase.DataSet as TClientDataSet;
  if Assigned(D) and ConfirmarBorrado then
    ManipuladorDatos.Eliminar(D);
end;

procedure TRejilla.DataSetUpdateExecute(Sender: TObject);
var
  D: TClientDataSet;
begin
  D := dsBase.DataSet as TClientDataSet;
  if Assigned(D) then
    ManipuladorDatos.Modificar(D);
end;

El esquema utilizado en los tres casos es el mismo. Se obtiene una referencia al dataset a través del datasource dsBase existente en TRejilla y si la referencia obtenida es válida, ejecutamos el código aplicable a cada acción. Este código se corresponde con la implementación que hace la clase TRejilla de la interfaz IManipulacionDatos. Esta interfaz esta definida unas  lineas antes que la propia clase y contendrá los 3 métodos, cada cual dando respuesta a las tres acciones básicas: modificar, insertar y eliminar. Una función, nos ayuda a obtener una referencia válida al método (recordad que ya comentábamos sobre este punto en la entrada anterior, por lo que podéis releer la parte A respecto a este punto).

function TRejilla.ManipuladorDatos: IManipulacionDatos;
begin
  if not Supports(Owner, IManipulacionDatos, Result) then
    GetInterface(IManipulacionDatos, Result);
end;

Siempre podéis opinar sobre este punto:  elegir un interfaz o por el contrario hacer uso de la herencia. A nuestros efectos, declarar como virtuales los métodos y no haber usado la interfaz IManipulacionDatos, podría tener un efecto similar, en cuanto a su contenido y a que puedan ser sobrescritos en el descendiente, si cambia o se amplia la funcionalidad de ellos. Sin embargo, parece que la guía al considerar una u otra  estrategia radica en la idea de la conveniencia de ligarse a una clase determinada y la oportunidad de obligar a que exista una relación de jerarquía sobre clases que pueden tener poco en común.

Y respecto a lo que hace ConfirmarBorrado es tan evidente que quizás sobren las lineas. No obstante, como en este caso Ian ha usado un cadena como recurso, vale la pena comentarlo y que pueda verse este punto.

resourcestring
  SConfirmacionBorrado = 'Va a eliminar este registro.'#13'¿Está seguro?';

function ConfirmarBorrado: Boolean;
begin
  Result := MessageDlg(SConfirmacionBorrado, mtConfirmation, [mbYes, mbNo], 0) = mrYes;
end;

Ummmm… :-)  Aun así, desgraciadamente existen usuarios cazurros para los que estas advertencias son papel mojado y a pesar de que uno advierte que van a perder los datos, exclaman con aires de fatalidad:

-No se… salvador… Dije que sí pero realmente no quería borrar… ¿Que puedo hacer? ¿He borrado los datos?

¡Dios mío! ¡Cuantas veces he llegado a escuchar cosas similares!. :-)   Para muchos de estos usuarios, blindar un programa para que no falle, llega a convertirse en un verdadero arte… jajaja pues uno tiene que entender que aunque no desean eliminar un registro, aun a pesar de pasar la vista sobre varias advertencias modales, son capaces de seguir adelante sin saber siquiera lo que han leído unos segundos antes… porque desgraciadamente nadie los lee…  :-)

Y respecto al evento de actualización de estado, en las acciones DataSetDelete y DataSetUpdate, han sido suficientes un par de lineas para saber si existen registros, por lo que nos basta asignar el método al evento en tiempo de diseño. Para el caso de insertar no importa esta consideración, y nos da igual que existan o no registros en el Dataset, aunque sí que podríamos incluir la condición Assigned(dsBase.DataSet).

procedure TRejilla.HayRegistros(Sender: TObject);
begin
  TAction(Sender).Enabled := Assigned(dsBase.DataSet) and
    not dsBase.DataSet.IsEmpty;
end;

Finalmente, la clase TRejilla también da respuesta al mensaje que genera el dbGrid que contiene en su interior, y que va a permitir editar o insertar sin tener que pulsar sobre los botones de la barra toolbar (mediante un doble click sobre el registro activo de la rejilla de datos), simplemente con el tratamiendo de ese mensaje, que originalmente procedía al redefinir el doble click en la clase TDBGrid. Eso era abordado igualmente en la parte A de esta quinta entrega.

procedure TRejilla.CMMostrarEditor(var M: TMessage);
begin
  if Assigned(dsBase.DataSet) then
    if dsBase.DataSet.IsEmpty then
      DataSetInsert.Execute
    else
      DataSetUpdate.Execute;
end;

No se si llegué a comentar entonces, que este punto hizo que perdiese varias jornadas de trabajo, sopesando la necesidad de que este mensaje, pudiera estar coordinado con el estado de las acciones DataSetInsert y DataSetUpdate también, pues era paradójico que una acción pudiera estar deshabilitada por motivos distintos de la cantidad de registros existentes en el dataset y siguiese activa mediante el acceso desde la recepción del mensaje, creando una contradicción evidente. Lo cual me llevó al final a añadir “algún” sistema que desactivara el mensaje como una propiedad mas del componente TRejilla.

Creo que podemos tomar un pequeño respiro antes de proseguir navegando sobre el código de la unidad. Vamos a concedernos unos minutos de reflexión y pensemos por un momento hacia donde nos esta llevando las consideraciones actuales:

  • tengo una rejilla que me muestra un conjunto de registros.
  • el usuario toma una decisión que afecta al futuro de uno de ellos.
  • y el conjunto de registros debe ser solidario con dicha decisión…

Ummmmmmmmmmmm…. ¿No se ve todavía claro el tema?

Debo de estar algo espeso esta noche: Voy a plantearlo de otra forma…

¿Cuando el usuario afecta a dicho registro existe un solo conjunto de datos o son al menos dos…?  :-)   Si fuera uno solo, y existiese un único dataset, los cambios efectuados sobre el mismo serían instantáneos pero claro, tendríamos un serio problema en el momento en el que añadieramos detalles enlazados, pues entendemos que la existencia de estos detalles vinculados al registro maestro van a ser algo consustancial a su existencia y el tamaño de la memoria y del traspaso de los datos a través de la red un bien escaso, en un  mundo donde nunca sabes a ciencia cierta, si esa cantidad de registros que va a albergar va a ser razonable o disparatada.

:-(

Así que nos aparece otra alternativa: tener dos datasets.  El primero ligado a la rejilla de datos, con la misión de representar el conjunto de datos que retorna la consulta del usuario. Y un segundo dataset parametrizado, ligado al registro que es afectado. A este segundo dataset, es al que iríamos enlazando los detalles, lo cual ya empieza a ser razonable. Cada vez que  se desea modificar o crear un nuevo registro, se recupera la información asociada al mismo si es una actualización o bien se inserta un registro vacío, de ser una inserción, y se deja al usuario operar sobre él, en esa ventana modal que representará cada descendiente de TDialogo. Al cerrarse dicha ventana, se guardaran los cambios o se desecharan.

Volviendo a la sentencia sql que abría la tabla de actividades, mientras que la sentencia del dataset de la rejilla se cargaría mediante la instrucción:

SELECT IDActividad,
       CodigoActividad,
       Nombre
FROM  dbo.Actividades
order by CodigoActividad

El segundo dataset en cambio, mostraría un código sql similar a:

SELECT IDActividad,
       CodigoActividad,
       Nombre
FROM  dbo.Actividades where IDActividad = :IDActividad

¿Quiere decir esto que ya no tenemos ningún problema…?  :-)

No… que va… la naturaleza del problema ha cambiado. Ahora tenemos que hacer creer a nuestro usuario que el primer dataset es el afectado porque de no ser así, si el usuario confirma los cambios y no sucede nada en la rejilla, será instantanea su queja:

- Hey… Salvador… me dijiste que la cerrar la ventana guardaría los cambios y sin embargo en la rejilla siguén igual…

Un consejo: No intentéis que lo comprenda el usuario… :-)  Simplemente hacedle creer que la realidad ha cambiado. Si el registro es modificado puede valernos sobrescribir los valores del antiguo registro con los nuevos. El usuario creerá realmente que el cambio en la rejilla obedece a que muestra los valores actuales de la base de datos y no la memoria de su caché. Si el registro ha sido insertado, basta añadir el nuevo registro al dataset vinculado a la rejilla. Y finalmente, cuando un registro va a ser borrado, para que vas a dejar que abra el editor… bórralo directamente del primer dataset y confirma los cambios sin que el segundo dataset sepa nada.

Ya nos dice el refrán que Quien siempre dice la verdad, puede permitirse tener mala memoria… :-)

Ahora ya estamos en condiciones de entender que sucede al ejecutar las acciones Insertar, Modificar y Eliminar. Veamos el código que Ian ha escrito para estos tres casos:

{ TRejilla: acciones de actualización }

function TRejilla.Insertar(D: TClientDataSet): Boolean;
begin
  if not Assigned(FEditor) then
    Result := False
  else
  begin
    D := AbrirDetalles(True);
    try
      D.Append;
      Result := FEditor.Ejecutar(Owner) = mrOk;
    finally
      CerrarDetalles;
    end;
  end;
end;

function TRejilla.Eliminar(D: TClientDataSet): Boolean;
begin
  D.Delete;
  Result := D.ApplyUpdates(0) = 0;
  if not Result then
    D.CancelUpdates;
end;

function TRejilla.Modificar(D: TClientDataSet): Boolean;
begin
  if not Assigned(FEditor) then
    Result := False
  else
  begin
    D := AbrirDetalles(False);
    try
      D.Edit;
      Result := FEditor.Ejecutar(Owner) = mrOk;
      if Result and Assigned(FDetalles) then
        CopiarRegistro(dsBase.DataSet as TClientDataSet, FDetalles);
    finally
      CerrarDetalles;
    end;
  end;
end;

Os he remarcado en color rojo, verde y azul, los detalles de la implementación que pueden necesitar una aclaración adicional, ya advertida en las lineas anteriores. Vamos a ver cada uno de ellos

  • FEditor representa una referencia a la clase TDialogo, ascendente de la que realmente va a instanciarse. Siguiendo el ejemplo en el que hacíamos referencia a las actividades del centro, deberíamos tener una unidad UActividades (descendiente de TBrowser), que alberga en su interior una instancia de TRejilla. También tenemos una unidad UActividad (descendiente de TDialogo) que va a ser enlazada al manipulador de datos para que pueda conocer cual de las instancias debe crear al editar o insertar. Esto supone que en el evento de creación de UActividades, existirán algunas lineas que deberán configurar el componente TRejilla para que actúe correctamente. Os pongo unas lineas de lo que podría ser el evento OnCreate() para la clase TActividades.
procedure TActividades.FormCreate(Sender: TObject);
begin
  inherited;
  Application.CreateForm(TActividadesSQLDatos, ActividadesSQLDatos);
  Application.CreateForm(TActividadesDatos, ActividadesDatos);
  RejillaActividades.Detalles:= ActividadesDatos.cdsActividad;
  RejillaActividades.Editor:= TActividad;  //  <<<------ !!!!!!
  RejillaActividades.TipoBusqueda('Todos los registros', CadenaSQL, True);
  RejillaActividades.Abrir(CadenaSQL);
end;
  • FDetalles, representa en cambio una referencia a lo que hemos podido determinar como segundo dataset. Le estamos indicando cual de ellos contiene el registro que va a ser afectado y como podreis ver en las dos siguientes métodos, en el caso de que esta asignación pueda no existir, se presupone que nuestro deseo es que sea afectado el primer dataset, es decir, apuntaría al datasource dsBase existente en nuestra Rejilla.
  • AbrirDetalles, CerrarDetalles y CopiarRegistro: Todos tienen en común en que giran alrededor del campo FDetalles, con la diferencia de que los dos primeros hacen referencia a la apertura y cierre del dataset mientras que CopiarRegistro, se centra en un momento posterior a la edición, una vez aceptados los cambios, momento en el que nos es necesario engañar al usuario y hacerle creer que la rejilla de datos contiene las modificaciones. Si os fijais, solo en el caso de que el campo FDetalles haya estado asignado, se procede a copiar el valor contenido en dicho registro con destino al dataset al que apunta la rejilla. :-)  Un truco sucio por parte de Ian pero muy efectivo… jajaja ¡Uno llega a disfrutar viendo cosas como estas…! Como es relativamente fácil la implementación de CopiarRegistro, sabiendo que tan solo existen dos parametros Origen y Destino, nos vamos a centrar directamente en conocer qué hacen AbrirDetalles y CerrarDetalles, no obstante, recordad que CopiarRegistro deberia tener en cuenta que una vez hecho el post en el dataset destino, se haría necesario llamar a MergeChangeLog, para aceptar los cambios y que nuestro Delta contega únicamente la nueva versión y no sean reenviados nuevamente, o bien invocar a CancelUpdate para deshacerlos de haber existido algun error. Este es el código que se implementa para dar respuesta a la apertura y cierre del segunda dataset.

function TRejilla.AbrirDetalles(Insertar: Boolean): TClientDataSet;
var
  I: Integer;
  f: TField;
begin
  Result := FDetalles;
  if not Assigned(Result) then
    Result := dsBase.DataSet as TClientDataSet
  else
  begin
    for I := 0 to Result.Params.Count - 1 do
    begin
      with Result.Params[I] do
        if Insertar then
          Value := -1
        else begin
          f:= Nil;
          try
             f:= dsBase.DataSet.FindField(Name);
          except
             ;
          end;
          if f  <> Nil then
            Assign(dsBase.DataSet.FieldByName(Name));
        end;
    end;
    Result.Open;
  end;
end;

procedure TRejilla.CerrarDetalles;
begin
  if Assigned(FDetalles) then
    FDetalles.Close;
end;

La clave para enteder que sucede al ejecutar AbrirDetalles es no perder de vista que siempre van a existir esos dos DataSets. Si entendeis que esto es así, resultará evidente que solo cuando estamos editando busquemos en los parámetros del mismo que campos deben ser encontrados en el primer dataset. Volved ahora la vista hacia el parámetro IDActividad, unas lineas mas arriba.

SELECT IDActividad,
       CodigoActividad,
       Nombre
FROM  dbo.Actividades where IDActividad = :IDActividad

:IDActividad (la clave primaria de la tabla) va a ser buscada recorriendo el array de campos comparando sus nombres con el parametro. Esta linea en AbrirDetalles nos lo dice:

f:= dsBase.DataSet.FindField(Name);

Así que, el hecho de que los parámetros y los campos tengan el mismo nombre no es casual. Tened en cuenta de que es una búsqueda donde se tienen en cuenta inclusive mayúsculas y minúsculas por lo que es necesario estar atento a este detalle. En el caso de que estemos insertando, nos basta asignar el valor -1, valor inexistente en el dominio de valores de las claves primarias de la tabla y que nos asegura que previamente no exista ningún registro en el Dataset.

Y ya para acabar, resta ver el método Abrir, usado para que nuestra rejilla de datos muestre los registros, y el destructor Destroy, que finalmente cierra el dataset. El fragmento mas significativo de este procedimiento es la asignación de la consulta dinámica que debe ejecutarse, representada por la función Generador que devolverá la cadena SQL.

procedure TRejilla.Abrir(Generador: TGeneradorSQL);

var
  D: TClientDataSet;
begin
  D := TClientDataSet(dsBase.DataSet);
  if Assigned(D) then
  begin
    D.Close;
    if Assigned(Generador) then
      D.CommandText := Generador()
    else
      D.CommandText := '';
    D.Open;
  end;
end;

destructor TRejilla.Destroy;
begin
  if Assigned(dsBase.DataSet) then
    dsBase.DataSet.Close;
  inherited Destroy;
end;

Os tengo que robar unos minutos más antes de despedirnos, con un comentario que hace referencia a código que no hemos visto aquí pero que está relacionado con la funcionalidad del manipulador de datos. Me explico: en alguna de las entradas anteriores, compartía con vosotros que el componente TRejilla también podía ser usado en el interior de la ventana modal, puesto que en la vida real, era posible y natural que una tabla detalle, a su vez, fuera maestra de otros detalles, y pudiera darse el caso de que vieramos la necesidad de que fueran editados simultaneamente. En la entrada primera de la serie incluiamos una imagen de un ejemplo de este punto.

Hay cambios pues, en ese caso, que deberían contemplarse y que pueden pasar desapercibidos. Por ejemplo, la ejecución del destructor Destroy en la rejilla, podría cerrar el Dataset asociado, ya que el formulario modal habitualmente puede ser destruido también. Esto habría que evitarlo en el caso de hacer uso de ese esquema. Y de la misma forma, convendría demorar el Applyupdates al momento en el que sea cerrada la ventana modal principal. En la imagen se puede ver claramente ésto: Cualquier modificación sobre la rejilla de faltas de Asistencia no debería ser consolidada en cuanto no sea aceptada la ventana modal del beneficiario, que es la que realmente acepta los cambios. Y si hablamos sobre el borrado de los registros sucede algo similar. Puede llegar a existir, por ejemplo, una acción adicional a la existente para el borrado (DataSetDelete) que no aplique cambios. La podríamos llamar DataSetDeleteEnDetalle, y nos permitiría borrar en los detalles pero no aplicar los cambios en tanto no fuera cerrada la ventana modal principal.

Bueno… ya os he dado la lata demasiado tiempo. Hasta aquí hemos compartido el framework, quedando a vuestra disposición todas las piezas para que puedan ser encajadas. Como complemente a estas entradas, me gustaría añadir un video donde se pueda ver visualmente como se compone este puzzle pero os confieso que voy muy ajustado de tiempo y que pueden pasar bastantes semanas antes de que lo pudiera grabar. Ni idea…

Como siempre os digo, espero que estas entradas os puedan servir de algo y que os ayuden, ya que no tienen otro fin. Y como hago en cada una de ellas, os animo a recabar mas información sobre los cursos de Ian Marteens, en el enlace a su web, que también figura en la sección de enlaces de la barra lateral del blog.

http://www.marteens.com/

Me resta darle las gracias a Ian por su generosidad, al aceptar que compartiera esta experiencia con la comunidad, como forma de aportar mi pequeño grano de arena.

Un saludo a todos.

Etiquetas: Delphi, frameworkm delphi datasnap midas, Ian Marteens, TAncestro, TBrowser, TDialogo, TRejilla
Comentarios desactivados

Un día con los mayores (5) Parte A

enero 17, 2010 en Ado Express & DataSnap, Artículos, ¿Sabías que...?, ¿Sabías que...? [Delphi], Código, Delphi, Entrada Diario, framework, Taller práctico

unit URejilla;
endunit URejilla;

En esta quinta parte de la serie, tenemos que abordar la clase TRejilla, que como ya comentabamos en las dos entradas previas, iba a servir de enlace entre la rejilla de datos y el formulario de edición (las clases TBrowser y TDialogo). La idea es que la sexta parte de por finalizada la serie, con independencia de que en un momento posterior encontremos tiempo para compartir comentarios que puedan ser interesantes. Nos estamos acercando al final.

Recordad las imagenes que mostraban las relaciones entre las clases y que compartiamos en la entrada tercera.

La primera imagen nos hablaba de las relaciones de herencia entre TAncestro, TBrowser y TDialogo.

La segunda, las relaciones de uso entre TBrowser, TDialogo y TRejilla.

En esa segunda imagen, se puede ver que existe (o puede existir *) una relación de uso, que es lo que realmente queríamos remarcar. Sin embargo, ésto expresado así, os podría inducir a error, ya que si tomamos el concepto expresado en dicha imagen literalmente, vendría a expresar que las clases TBrowser o TDialogo se componen de una rejilla, o dicho de otra forma, que contienen en su interior una instancia de la clase. Y realmente, la imagen era meramente descriptiva de la relación. En nuestro caso, únicamente serán los descendientes de TBrowser o de TDialogo, quienes podrán contenerlas, ya que de no ser así nos ataríamos a un esquema de trabajo demasiado rígido.

(*) Aunque el ejercicio 13 que contiene el framework de Marteens no contiene  descendientes de TDialogo que hagan uso del componente TRejilla, comentabamos que es factible de ser usado, ya que pueden existir detalles que dependan de los detalles del dataset maestro. Si recordáis la primera entrada, en ella existía una imagen de la aplicación en ejecución, donde se podía ver en el interior de la ventana que representa la ficha del beneficiario, un detalle de suscripciones que se resuelve con la ficha de edición de suscripciones y que a su vez contiene la rejilla de asistencias. Esta era la imagen.

Para lo que pueden sentirse perdidos a estas alturas, estamos presentando sobre nuestra mesa de trabajo las distintas piezas que van a participar en el framework de Ian Marteens, para, una vez hecha la presentación,  abordar de forma global la puesta en marcha de todo el conjunto, tarea que seria abordada en una ultima entrada (la sexta y definitiva). El ejemplo que ha dado sentido a la serie debería, en teoría, ser razonado desde el puzzle que tenemos en nuestras manos.

Recapitulemos...

En estos momentos, nuestra aplicación debería contener el módulo principal (UPrincipal.pas), dos datamodules (UDatos.pas y USQLDatos.pas) que pueden estar creados pero que todavía no contienen ningún componente, ya que si recordáis lo comentado en la entrada 2, esa parte la íbamos a abordar al hacer referencia a Datasnap, dentro de la puesta en marcha del framework.

Tambien deberíamos haber generado el modulo UAncestro.pas, conteniendo la clase base, de acuerdo a las indicaciones de la tercera parte de la serie. Y finalmente, añadiendo a nuestro proyecto UBrowser.pas y UDialogo.pas respectivamente, creados ambos tomando como ascendente la clase TAncestro, ubicada en la unidad UAncestro.pas.

Aunque en aquellos momentos no concretamos nada, es un paso sencillo. Para ello, bastaba ir al menú principal del entorno y hacer

File -> New -> Other, seleccionando la unidad deseada, en nuestro caso TAncestro.

Y una vez creadas ambas unidades (UBrowser.pas y UDialogo.pas), añadir el contenido razonado en Un día con los mayores (4) para cada una de ellas. La simple inspección ocular del código nos indica que TBrowser contiene en su interior una TToolBar, que en un futuro habilitaría las distintas acciones disponibles del conjunto de datos (insertar, eliminar, editar, buscar, etc...). TDialogo, por el contrario solo necesita un par de botones para aceptar y cancelar la edición (la pulsación del primero debería producir el valor mrOk, mientras que el segundo haría lo propio con el valor mrCancel) y un groupbox (TGroupBox) incluido tan solo para contener los distintos controles de datos agrupados.

Ahora ya podemos proseguir...

Vamos a crear la unidad URejillla.pas, que contendrá el componente TRejilla. Accedemos al menú del entorno.

File -> New -> Delphi Files, seleccionando la unidad deseada, en nuestro caso Frame.

Al aceptar el experto, vamos a obtener un módulo que contiene el contenido mínimo y que incluye la declaración de tipos del descendiente de TFrame.

PLAIN TEXT
DELPHI:
  1. unit Unit1;
  2. interface
  3. uses
  4. Windows, Messages, SysUtils, Variants, Classes, Graphics,
  5. Controls, Forms, Dialogs;
  6. type
  7.  TFrame1 = class(TFrame)
  8.  private
  9.   { Private declarations }
  10.  public
  11.   { Public declarations }
  12. end;
  13. implementation
  14. {$R *.dfm}
  15. end.

Asignamos las propiedades Name del componente para que tome el valor Rejilla y guardamos la unidad con el nombre propuesto (URejilla.pas).

Nos resta tan solo añadir el contenido a nuestro Frame y para ello, podemos hacerlo en tiempo de diseño (una de las ventajas, entre otras, que sin duda inclinaría a Ian Marteens a elegirlo como contenedor base del componente).

Veamos que incluye Ian en el:

Lo mas importante es la rejilla de datos (TDBGrid) ya conocida por todos ya que  nos ha acompañado desde siempre. También vamos a añadir un datasource. La rejilla se conectará al mismo a través de la propiedad DataSet, como se puede suponer. El tercero en discordia es el componente TActionList, al que podemos ya añadir las acciones predefinidas para gestión de Datos, ligadas de igual forma al datasource. Y finalmente, nos resta añadir un componente Tpopupmenu, que puede asignarse a la propiedad PopupMenu del DBGrid y contener las acciones deseadas del componente contenedor de las acciones.

Esta podría ser una imagen de nuestro Frame, tras seguir los pasos comentados. He incluido en la imagen los nombres junto a cada uno de ellos.

TRejilla

Y en las dos siguientes imágenes, podéis apreciar el contenido del contenedor de acciones y del popupmenu. Así queda un poco mas claro.

Contenido LocalActions

Conteniod UpdatePopup

Permitid me un comentario al hilo de la imagen que muestra el contenido de TRejilla.  Es algo que ya hemos recalcado desde el principio: En la primera de estas 3 imágenes, existen dos componentes que se encuentran tachados. Los he dejado a la vista, porque originalmente, el framework de Marteens, incorpora una buena cantidad de detalles que solo tienen sentido dentro de un cursos tan ambiciosos como cualquiera de los planteados por el. Desgraciadamente en el ámbito de estas entradas, que son tan breves, es imposible no recortar el contenido, mas cuando la filosofía que las ha generado es simplemente la de compartir una experiencia de uso del mismo. En el framework se implementa un mecanismo sencillo y muy didáctico de selección de búsquedas que simplificaremos al máximo (de ahí que se haya excluido el componente sobre el que se centra la acción, que es el popupmenu tachado). La impresora en cambio, representa el dialogo de impresión, funcionalidad a la que previamente habíamos renunciado por el mismo motivo. Y, aunque aquí no van a aparecer, dentro del código fuente, también se implementa los procedimientos que permitirán que las rejillas sean capaces de implementar la funcionalidad de drag&drop, de forma transparente para el proceso posterior de montaje de la aplicación.

No obstante, el código que resta es muy interesante, como creo que al final coincidiréis.

El por qué ha elegido Ian Marteens la clase TFrame como clase contenedora es la primera reflexión que nos podríamos plantear ya que TRejilla también podría haber sido creada partiendo de otro ascendiente. Existen numerosas clases que tienen esta funcionalidad de albergar componentes en su interior (se me ocurre TPanel por ejemplo) pero a diferencia de ellas, no podríamos diseñar ni formaría parte del repositorio de objetos, con la flexibilidad y potencia que ésto nos puede aportar. Ese punto es vital. Es mas, el planteamiento aprovecha al máximo todo lo bueno que puede darnos la clase TFrame, ya que no siempre resulta "fácil" su uso en planteamientos que impliquen la creación dinámica de los mismos y los accesos en tiempo de ejecución a los componentes que lo componen y que dotan la funcionalidad deseada. Un ejemplo de este tipo de uso podría ser frameworks similares al compartido por nosotros en aquella entrada titulada Un enfoque modular para nuestra aplicación. Si habéis leído la serie que se publicó en los Boletines 13, 14, 15 y 16 del Rinconcito de Delphi creo que entenderéis a que hago referencia.

Es mas... antes de meternos de lleno en el código, permitirme por favor otro alto en este figurado camino. Permitid que os dirija a la siguiente página dentro de la web del escritor:

http://www.marteens.com/trick46.htm

En ella se habla de un concepto conocido como "interponer una clase", que resulta extremadamente útil en este contexto como forma práctica y eficaz de extender la funcionalidad de los componentes sin tener que hacer instalaciones en el entorno. Leedla con tranquilidad porque es muy interesante y no tiene desperdicio. Marteens también se vale de esta técnica al abordar su curso y la aplica en varias ocasiones. En este caso concreto, es usada para cambiar el comportamiento del componente DBGrid contenido en la rejilla, de forma que todos los módulos que hagan uso de ella y declaran UGrids tras la unidad Grids de la VCL, redefinen su comportamiento. Así de práctico y de sencillo.

El motivo que le lleva a considerar su uso es dotar al componente DBGrid de persistencia (las dimensiones de sus columnas son recordadas en tiempo de ejecución permitiendo de forma transparente que sea configurada a gusto de nuestro usuario), permitir la ordenación de los campos tras hacer click sobre las cabeceras de cada columna, drag&drop a nivel de registro y finalmente, y la que me parece particulamente clave, que es sobrescribir el procedimiento virtual que afecta al doble click para que la rejila pueda dar tratamiento a la edición del formulario.

Extraemos un fragmento de la unidad incorporando únicamente los aspectos relacionados con la ordenación y con el procedimiento DblClick.

PLAIN TEXT
DELPHI:
  1. </p>
  2. <pre>unit UGrids;
  3.  
  4. interface
  5.  
  6. uses Windows, Messages, Classes, SysUtils, Graphics, Controls, Grids, DBGrids,
  7.   DB, DBClient, UAncestro;
  8.  
  9. const
  10.   CM_MOSTRAREDITOR = WM_USER + 1;
  11.  
  12. type
  13.   TDragControlObjectExClass = class of TDragControlObjectEx;
  14.  
  15.   TDBGrid = class(DBGrids.TDBGrid)
  16.   protected
  17.     procedure DblClick; override;
  18.     procedure TitleClick(Column: TColumn); override;
  19.   public
  20.   end;
  21.  
  22. implementation
  23.  
  24. { TDBGrid }
  25.  
  26. procedure TDBGrid.DblClick;
  27. begin
  28.   inherited DblClick;
  29.   if Assigned(Parent) and Parent.HandleAllocated then
  30.     PostMessage(Parent.Handle, CM_MOSTRAREDITOR, 0, 0);
  31. end;
  32.  
  33. procedure TDBGrid.TitleClick(Column: TColumn);
  34. var
  35.   S: string;
  36. begin
  37.   try
  38.     S := Column.FieldName;
  39.     with DataSource.DataSet as TClientDataSet do
  40.       if IndexFieldNames &lt;&gt; S then
  41.         IndexFieldNames := S
  42.       else
  43.       begin
  44.         AddIndex(S, S, [], S);
  45.         IndexName := S;
  46.       end;
  47.   except
  48.   end;
  49.   inherited TitleClick(Column);
  50. end;
  51.  
  52. end.</pre>
  53. <p>

El evento OnDblClick de la rejilla sigue así estando disponible para su uso y se disparará siempre previo al envío del mensaje.

En el mundo real, me resultó de ayuda añadir un mensaje adicional

CM_EVENTOMOSTRAR = WM_USER + 2;

que pudiera ser entregado tras la creación del formulario modal a la ventana browser que generaba la llamada, ya que aunque sí podía redefinir las acciones en el descendiente al insertar o modificar, no resultaba tan sencillo capturar el doble click, oculto por el componente TRejilla, que le daba tratamiento tras el envío del mensaje por el componente DBGrid. Dado que en el contexto de ejecución que activa el doble click, la ventana activa seguía siendo el formulario browser, no era demasiado complicado añadir un mensaje que pudiera ser tratado por este según nuestra necesidad. 

procedure TDBGrid.DblClick;
begin
  inherited DblClick;
  if Assigned(Parent) and Parent.HandleAllocated then begin
    PostMessage(Parent.Handle, CM_MOSTRAREDITOR, 0, 0);
    PostMessage(Screen.ActiveForm.Handle, CM_EVENTOMOSTRAR, 0, 0);
  end;
end;

Otras veces, pensé que podría ser me de interés la creación de un evento previo y posterior a la comunicacion de los mensajes. En tales casos, escribía la implementación:

procedure TRejilla.CMMostrarEditor(var M: TMessage);
begin
  //antes de gestionar el mensaje
  if Assigned(FOnBeforeOpenEditor) then FOnBeforeOpenEditor(Self);
  if Assigned(dsBase.DataSet) then
    if dsBase.DataSet.IsEmpty then
      DataSetInsert.Execute
    else
      DataSetUpdate.Execute;
  //despues de gestionar el mensaje
  if Assigned(FOnCloseEditor) then FOnCloseEditor(Self);
end;

añadiendo en la parte publica de TRejilla, las propiedades respectivas:

    property OnBeforeOpenEditor: TNotifyEvent read FOnBeforeOpenEditor write SetOnBeforeOpenEditor;
    property OnCloseEditor: TNotifyEvent read FOnCloseEditor write SetOnCloseEditor;

Lo cual me daba la posibilidad de implementar una respuesta desde el formulario de navegación.

Ahora ya estamos en condiciones de poder abordar el código y resaltar los puntos claves que nos permitirán comprenderlo.

Lineas mas abajo podemos ver un extracto del interfaz y la declaración de tipos. Tras la sección correspondiente a los "uses", que hacen referencia a unidades enlazadas, es declarada la interfaz IManipulacionDatos, que concentra lo que pueden ser las tres acciones básicas de manipulación de datos: la inserción, el borrado y la modificación. Un poco mas abajo, TGeneradorSQL, permite de forma generica establecer la que será la cadena SQL que abre la consulta de nuestra rejilla de datos. Declararla de esta forma, nos obliga a que el proveedor establezca dentro de sus opciones poAllowCommandText, pero nos abre un abanico grande de posibilidades, tanto de forma dínamica que es como Marteens establece las busquedas o bien, estática, que es como de forma sencilla nos la plantearemos nosotros. Finalmente, es declarado el tipo TRejilla.

PLAIN TEXT
DELPHI:
  1. </p>
  2. <pre>unit URejilla;
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7.   Dialogs, DBActns, ActnList, DB, DBClient, Grids, DBGrids, Menus,
  8.   Contnrs, UAncestro, UDialogo, UDatos, Printers, UGrids;
  9.  
  10. type
  11.   IManipulacionDatos = interface
  12.   ['{DCCCAB14-F5F8-49C5-8E95-E2E1E8A79749}']
  13.     function Insertar(D: TClientDataSet): Boolean;
  14.     function Eliminar(D: TClientDataSet): Boolean;
  15.     function Modificar(D: TClientDataSet): Boolean;
  16.   end;
  17.  
  18.   TGeneradorSQL = function : string of object;
  19.  
  20.   TRejilla = class(TFrame, IManipulacionDatos)
  21. ...
  22.   protected
  23.     FUltimaBusqueda: TMenuItem;
  24.     FEditor: TDialogoClass;
  25.     FDetalles: TClientDataSet;
  26.     function ManipuladorDatos: IManipulacionDatos;
  27.     procedure CMMostrarEditor(var M: TMessage); message CM_MOSTRAREDITOR;
  28.     // IManipulacionDatos
  29.     function Insertar(D: TClientDataSet): Boolean;
  30.     function Eliminar(D: TClientDataSet): Boolean;
  31.     function Modificar(D: TClientDataSet): Boolean;
  32.     property Grid: TDBGrid read DBGrid;
  33.   public
  34.     destructor Destroy; override;
  35.  
  36.     procedure Abrir(Generador: TGeneradorSQL = nil);
  37.     function AbrirDetalles(Insertar: Boolean): TClientDataSet;
  38.     procedure CerrarDetalles;
  39.  
  40.     property Detalles: TClientDataSet read FDetalles write FDetalles;
  41.     property Editor: TDialogoClass read FEditor write FEditor;
  42.   end;</pre>
  43. <p>

¿Qué papel juega cada elemento?

Solo voy a comentar ahora, que la propiedad publica Editor, es la que nos permite establecer una relación entre la ventana de navegación y la de edición modal. Al hacer doble click sobre el componente TRejilla o ejecutar las acciones de edición o inserción, finalmente llegaríamos a una linea de codigo

FEditor.Ejecutar(Owner) = mrOk;

que se resolvería en el ancestro de nuestra ventana de dialogo. La ejecución del metodo de clase Ejecutar lanzaría la edición de la ficha.

class function TAncestro.Ejecutar(AOwner: TComponent): TModalResult;
begin
  if AOwner = nil then
    AOwner := Application.MainForm;
  with Create(AOwner) do
  try
//    RegistroLeer;
    Result := ShowModal;
  finally
    Free;
  end;
end; 

En muchas ocasiones he intentado compartir con vosotros, a lo largo de las entradas del blog, puntos en los que la ganancia de pensar en clases y de usar la herencia nos permitían llegar un poco mas alla de las meras asignaciones, y estas lineas que nos permiten compartir el framework de Ian, responden con creces a esta idea.     

:-)

Lo dejamos aquí.

Os dejo con la intriga...

En la parte B de esta quinta entrega abordamos la implementación y nos quedaria la sexta y final, en la que vamos a intentar armar el puzzle para que las piezas encajen. 

Espero que estas entradas os puedan servir de algo y que os ayuden. Y como hago en cada una de ellas, os animo a recabar mas información sobre los cursos de Ian, en el enlace a su web, que también figura en la sección de enlaces de la barra lateral del blog.

http://www.marteens.com/

Etiquetas: Delphi, frameworkm delphi datasnap midas, Ian Marteens, TAncestro, TBrowser, TDialogo, TRejilla
Comentarios desactivados

Un día con los mayores (4)

noviembre 22, 2009 en Ado Express & DataSnap, Artículos, ¿Sabías que...?, ¿Sabías que...? [Delphi], Código, Delphi, Entrada Diario, framework, Taller práctico

Vamos a dar otro paso más...

En la tercera entrada de esta pequeña serie, nos centrábamos en la clase TAncestro, y pudimos ver como se convertía en la piedra angular del framework de Ian, de la que iba a heredar cualquier tipo de ventana que pudieramos pensar o necesitar.

Ian valora en sus ejemplos, dos arquetipos de ventanas diferenciados: el que representa a la clase TBrowser y el que representa a la clase TDialogo. Es su idea, dicho sea de paso, muy razonable, de acuerdo a su experiencia como experto y en ese intento de simplificar. Quizás en vuestro caso, mientras leéis estas lineas imaginéis otros contextos que hagan necesaria la creación de otros descendientes, de acuerdo a criterios mas selectivos, como por ejemplo pudiera ser la seguridad, donde ciertas ventanas pudieran necesitar un tratamiento especial. Nada es descartable y el framework debería ser una propuesta de trabajo que muchos van a adaptarse de acuerdo a sus necesidades. De hecho, os comento que en mi caso concreto y en lo que respecta a la funcionalidad del Framework, descarté algunas partes que no estaba utilizando, como era la posibilidad de arrastrar registros mediante drag&drop, que Ian sí introduce y pone en práctica. Respecto a las ventanas, al final no me fue necesario crear ningún tipo adicional aunque hubiera momentos en los que sí pude habérmelo planteado.

En el caso que nos ocupa existe un primer tipo, necesario para mostrar aquellos registros que el usuario demandará, de acuerdo a ciertos criterios no siempre sabidos de antemano. La idea es mostrar solo lo que el usuario necesita. El sentido común debería prevalecer siempre para seleccionar qué datos son los que realmente va a necesitar nuestro usuario, que por sistema demandará "todo". Es razonable darle lo que realmente necesita ¿no os parece?. Mi padre siempre me recordaba un refrán que decía que contra el vicio de pedir estaba la virtud de no dar. :-)

También debería ser razonable (y ciertamente también discutible) que la ventana que va a mostrar los distintos registros no devolviera inicialmente ninguno y que fuera el usuario quien nos dijera que va a necesitar. Bueno... a nosotros no... me refiero a la aplicación. Marteens muestra un sistema muy sencillo de búsquedas que van a permitir que el usuario, mediante una elección, pueda satisfacer esa demanda de información.

De todo esto, y como resumen, queda la idea de que nuestro primer tipo, la clase TBrowser, va a servir para explorar los datos, permitirá navegar sobre esta información (algo usual en estas aplicaciones) y que nuestro usuario pueda también sobre ellos ejecutar determinados procesos en aras de "determinados" resultados. Cualquier caso que se nos ocurra puede ser oportuno: la lista de clientes que mas nos han comprado de cada provincia, aquellos que pertenecen a una provincia concreta,  etc. Habitualmente pretenderá acciones conocidas por todos, como borrar, modificar o insertar, pero bien nos puede valer para extender a procesos que puedan recaer no ya sobre un registro concreto sino una colección de ellos. Esa es un poco la idea que justifica y da vida a la clase TBrowser.

Recordemos una imagen de la ventana, en la que existía un componente contenedor de acciones TActionList y una barra de botones TToolBar, que mostrará visualmente las distintas acciones disponibles para el conjunto de datos mostrados al usuario.

Esta es la imagen:

A continuación, mostramos el código, que realmente no va a extender el comportamiento de TAncestro, mas en la capacidad para imprimir el contenido de la información que mostrará la ventana. Todas estas lineas realmente son opcionales.

Ved el código que ha implementado Ian y comentamos:

PLAIN TEXT
DELPHI:
  1. <pre>unit UBrowser;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7.   Dialogs, UAncestro, ComCtrls, ToolWin, ActnList, ImgList, UImpresion;
  8.  
  9. type
  10.   TBrowser = class(TAncestro, IImprimible)
  11.     CoolBar: TCoolBar;
  12.     ToolBar: TToolBar;
  13.    protected
  14.       procedure Imprimir; virtual;
  15.   end;
  16.  
  17. implementation
  18.  
  19. {$R *.dfm}
  20.  
  21. { TBrowser }
  22.  
  23. procedure TBrowser.Imprimir;
  24. var
  25.   I: Integer;
  26.   Imp: IImprimible;
  27. begin
  28.   for I := 0 to ControlCount - 1 do
  29.     if Supports(Controls[I], IImprimible, Imp) then
  30.     begin
  31.       Imp.Imprimir;
  32.       Exit;
  33.     end;
  34. end;
  35.  
  36. end.

Llegado este punto y dado que hemos utilizado la referencia en el uses a la unidad UImpresion, deberíamos también darle un mínimo de contenido para seguir adelante, con independencia de que no comentemos la implementación concreta que ha sido utilizada para imprimir los registros, puesto que esto es algo que podemos concretar de acuerdo a nuestras necesidades.

Como mínimo debería contener:

PLAIN TEXT
DELPHI:
  1. unit UImpresion;
  2.  
  3. interface
  4.  
  5. uses Classes, SysUtils, Graphics, Printers, DB, DBGrids;
  6.  
  7. type
  8.   IImprimible = interface
  9.   ['{267E5074-DF76-4ACE-ACCB-2BA6831F193A}']
  10.     procedure Imprimir;
  11.   end;
  12. ...

Marteens se apoya y desarrolla una función con ambito global al modulo, con esta firma:

function CreateReport(const ATitle: string; AGrid: TDBGrid): IInforme;

function CreateReport(const ATitle: string; AGrid: TDBGrid): IInforme;

que va a ser llamada por aquellos objetos creados que hagan uso del interfaz IImprimible, dando respuesta al contrato que concluye el procedimiento Imprimir.

Vosotros podéis crear vuestra propia función, pero debéis de tener en cuenta algo muy importante y es que para este nivel de abstracción, no conocéis que campos concretos van a existir y debéis actuar siempre bajo esa premisa... :-)  Debería ser una función que en muchos puntos, lógicamente recorrerá estructuras apoyándose en las propiedades especificas para delimitar los dominios de las mismas. Un ejemplo para verlo con mas claridad: Podría ser una rejilla el objeto de nuestra atención y podría también ser la estructura genérica de columnas que contiene la rejilla, la que nos permitiera descubrir los distintos campos del informe. La propiedad Count de la clase TColumn, mantiene el total de columnas existentes en la matriz y cualquier bucle típico (for do, while do, etc..) recorrerá la matriz y nos permitirá acceder a la información deseada.

El como se monte este código es un tanto lo de menos. Ian simplifica al máximo y crea un informe genérico que nos pueda valer para cualquier rejilla que muestre datos al usuario. De esa forma, economizamos nuestro esfuerzo y nos despreocupamos de este tipo de detalles que suelen ser bastante ingratos y absurdos.

En la declaración de tipos anterior, nos aparece la declaración de la interfaz IImprimible, que consta de un único procedimiento, acompañada del CLSID o GUID, propios de una interfaz COM.

Volviendo a la idea comentada,  yo acabé añadiendo unas rutinas que permitían exportar a Excel el contenido de cualquier información mostrada por las ventanas descendientes de TBrowser. ¿Qué tal crear otra interfaz IExportable, tal que pudiera considerar esta opción? Son decisiones que van a depender de vuestro contexto de trabajo para valorar que pueda ser necesarias o no lo sean. En cualquier caso, no resulta demasiado difícil descubrir que existen colgando de la red de redes distintas implementaciones que hagan precisamente esto. Así que pudiera ser una buena excusa para dar un vistazo y ver si puede ser usada para nuestro fin.

No perder de vista la implementación del método Imprimir que hace TBrowser. Este punto es clave.

  for I := 0 to ControlCount - 1 do
    if Supports(Controls[I], IImprimible, Imp) then
    begin
      Imp.Imprimir;
      Exit;
    end;

Cumplimos nuestro contrato y además delegamos la impresión en cualquier objeto que soporte la interfaz IImprimible, contenido en nuestra instancia descendiente de TBrowser. Lógicamente hacemos algo de trampa. Nosotros no... Ian, que es mas pillo que bonico, que dirían en mi pueblo... :-)   porque estamos suponiendo que solo existe un objeto que implemente la interfaz. Pero es nuestro supuesto y partimos de que no va a existir mas de una rejilla en cada explorador de datos. ¿Podría darse el caso de que nuestro explorador de datos tuviera un maestro y un detalle? Son cosas que nadie lo sabe salvo el desarrollador que tiene entre manos el proyecto y estudia el interfaz adecuado a esa información.

Supports, es una función implementada en el modulo SysUtils que nos permitirá saber si el objeto (en este caso concreto representado por la matriz Controls[I]), usa el contrato, permitiendo referenciar en la variable Imp de tipo IImprimible (del tipo de nuestra interfaz) una forma de acceder a la instancia y ejecutar el método en cuestión.

Si el control no declara la interfaz en alguno de sus ascendientes o en él mismo, la función lo ignorará. Si encuentra alguno, no buscará mas.

Y respecto a la segunda unidad, tampoco el código es excesivo. Esta es la imagen de la ventana que representa a TDialogo y el código presente en el módulo usado por Ian en el curso:

PLAIN TEXT
DELPHI:
  1. unit UDialogo;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7.   Dialogs, UAncestro, DB, DBClient, StdCtrls, ActnList, ImgList;
  8.  
  9. type
  10.   TDialogoClass = class of TDialogo;
  11.  
  12.   TDialogo = class(TAncestro)
  13.     dsBase: TDataSource;
  14.     BasePanel: TGroupBox;
  15.     bnOK: TButton;
  16.     bnCancel: TButton;
  17.     procedure FormKeyPress(Sender: TObject; var Key: Char);
  18.   protected
  19.     function Modificado: Boolean; override;
  20.     procedure Guardar; override;
  21.     procedure Descartar; override;
  22.   end;
  23.  
  24. implementation
  25.  
  26. {$R *.dfm}
  27.  
  28. { TDialogo }
  29.  
  30. function TDialogo.Modificado: Boolean;
  31. begin
  32.   if not Assigned(dsBase.DataSet) then
  33.     Result := False
  34.   else
  35.     with dsBase.DataSet as TClientDataSet do
  36.       if Assigned(DataSetField) then
  37.         Result := Modified
  38.       else
  39.         Result := Modified or (ChangeCount&gt; 0);
  40. end;
  41.  
  42. procedure TDialogo.FormKeyPress(Sender: TObject; var Key: Char);
  43. begin
  44.   inherited;
  45.   if (Key = #13) then
  46.   begin
  47.     Perform(WM_NEXTDLGCTL, 0, 0);
  48.     Key := #0;
  49.   end;
  50. end;
  51.  
  52. procedure TDialogo.Guardar;
  53. begin
  54.   if Assigned(dsBase.DataSet) then
  55.     with dsBase.DataSet as TClientDataSet do
  56.       if Assigned(DataSetField) then
  57.         CheckBrowseMode
  58.       else if ApplyUpdates(0)&gt; 0 then
  59.         Abort;
  60. end;
  61.  
  62. procedure TDialogo.Descartar;
  63. begin
  64.   if Assigned(dsBase.DataSet) then
  65.     with dsBase.DataSet as TClientDataSet do
  66.       if Assigned(DataSetField) then
  67.         Cancel
  68.       else
  69.         CancelUpdates;
  70. end;
  71.  
  72. end.

Este segundo tipo de ventana, responde a la edicion/inserción de datos de nuestro explorador, representado como ya hemos comentado por la clase TBrowser. El usuario seleccionará uno de los registros, y esta ventana (TDialogo), nos permitirá recoger esos cambios.

Marteens nos hace una propuesta donde TDialogo es modal. La pregunta sobre la bondad o maldad de ser así ya es otro cantar. Yo también encuentro mas sencillo este esquema, que trabajar sobre el supuesto de un formulario no modal. De hecho, no me he planteado para este tipo de aplicación un esquema distinto puesto que funciona bien, ya que le permites a tu usuario libertad a nivel del explorador de datos con la única condición de que concluya las operaciones. ¡No todo van a ser ventajas para nuestro usuario mimado. Lo cual, introduce cierta inquietud sobre el hecho mismo de considerar que puedan no existir las condiciones necesarias para que concluya el proceso de edición con éxito, lo cual suele ser salvado mayormente creando ventanas auxiliares que favorezcan cumplir esas condiciones. Pero claro... tiene que existir un limite en esa recurrencia.

Originalmente el curso trae los procedimientos Modificado, Guardar y Descartar, que eran tres de las acciones abstractas, ya comentadas en la entrada anterior. Creo recordar que cansado de añadir la implementación del evento OnKeyPress de cada formulario, acabé escribiendo la rutina con la idea de asignarla en el descendiente si me era necesario.

Respecto a los tres procedimientos, quizás lo veáis mas claro pensando que pueda ir ligada su invocación sobre una fuente de datos que apunta al registro maestro o haga referencia a uno de sus detalles. Dado que no sabe a ciertas, desde este punto el origen de la llamada, no puede mas que prevenir que sea el ClientDataSet que soporta la acción (representado en dsBase.DataSet) sea el maestro de datos, en cuyo caso no tendría asignado el campo DataSetField. De ser al contrario, si dsBse.DataSet apunta a una fuente de datos que representa un detalle del registro maestro, existiría esta asignación. Marteens actua en consecuencia. En este punto quizás podría seros util recordar una entrada que compartimos tiempo atrás y que hablaba de esto precisamente:

(Entradas: Por curiosidad I, II y III) http://sjover.com/delphi/?p=128 ,  http://sjover.com/delphi/?p=129 y http://sjover.com/delphi/?p=130

En los tres casos, Modificado, Guardar y Descartar hacen cosas distintas según el caso. Intentaremos comentar esto con mas detalle cuando abordemos la parte que hace referencia a datasnap.

Espero que estas entradas os puedan servir de algo y que os ayuden. Y como hago en cada una de ellas, os animo a recabar mas información sobre los cursos de Ian, en el enlace a su web, que también figura en la sección de enlaces de la barra lateral del blog.

http://www.marteens.com/


Etiquetas: Delphi, frameworkm delphi datasnap midas, Ian Marteens, TAncestro, TBrowser, TDialogo, TRejilla
1 Comentario »

Un día con los mayores (3)

octubre 12, 2009 en Ado Express & DataSnap, Artículos, ¿Sabías que...?, ¿Sabías que...? [Delphi], Código, Delphi, Entrada Diario, framework, Taller práctico

Se podría decir que, al iniciar esta tercera parte de la serie, en la que vamos a razonar sobre el framework de los cursos de Ian Marteens, nos introducimos en una de las areas más bonitas del mismo, donde los razonamientos formales y la abstracción se destacan sobre otros aspectos más mecánicos y menos atractivos (visto ésto como desarrolladores). No se si coincidireis o no, pero no existe demasiado "mérito" en hacer la asignación de una propiedad ni resulta algo demasiado creativo, salvo que lo vivamos desde la perspectiva del creador de componentes. Más bien, forma parte del elenco de actividades repetitivas, que hacemos diariamente de forma mecánica.

Sin embargo, en este caso, establecer relaciones de herencia y uso entre las entidades, es un ejercicio estimulante y enriquecedor. Marteens inicia su periplo al definir una clase base (TAncestro), en la cuspide de la jerarquía que define el dominio de la aplicación. Y conviene decir esto ya que TAncestro no nace de la nada puesto que hereda el comportamiento de la clase TForm, de la cual desciende. Lo cual, nos permite disponer de toda la funcionalidad de los formularios que vamos a manipular. ¿Y por que es conveniente que sea así? Pues como bien explica en los primeros parrafos del ejercicio 3 de la serie c :

"A partir de determinado tamaño de aplicación, se hace necesario organizar las ventanas de un proyecto de acuerdo a una jerarquia de herencia. El principal motivo es evitar tareas repetitivas cada vez que se añade una nueva ventana durante el desarrollo. Pero una vez que ya existen las ventanas prototipos, existen ventajas innegables para el mantenimiento de las mismas."

La palabra clave aquí es Prototipo. Esa es la clave con la que deberiais quedaros. Es decir, buscamos prototipos que simplifiquen los procesos de creación de entidades, que permitan gastar el mismo esfuerzo en el proceso de construcción, a todos los niveles. No reinventar la rueda en cada nuevo formulario en todo aquello que va a ser comun a todos. Sí. Estoy seguro de que la mayoría coincidirá en esto, aunque luego el día a día, haga que no siempre podamos avanzar afianzando la base del edificio y tengamos que conformarnos en dar una mayor altura al mismo, limitandonos a constuir un piso mas.

Pero lo mejor, es verlo con dos imagenes en las que se muestran dos de las relaciones tradicionales que se pueden establecer entre distintas entidades: "es una" y "usa una". Una relación de herencia y otra de uso. En las imagenes van a aparecer las clases claves dentro de la estructura del framework y nos permitiran tener una visión global y amplia del mismo.

Relaciones de Herencia entre TAncestro, TBrowser y TDialogo.

Relaciones de Uso entre TBrowser y TRejilla.

Sobre la primera imagen, se definen las relaciones de herencia entre TAncestro y TDialogo, y TAncestro y TBrowser. En ambos casos, TAncestro es el ascendente de ambas clases, que servirán a su vez como ascendente de aquellas ya especificas de cada modulo. Mientras los descendientes de TBrowser nos ayudarán a seleccionar aquellos registros que van a ser requeridos por el usuario (valiendose de una instancia de TRejilla), los descendientes de TDialogo serán figuradamente la ficha sobre la que vamos a editar.

Sobre la última imagen, es obligado hacer un pequeña reflexión puesto que en la misma, puede verse en la parte izquierda como se ha colado sobre un recuadro definido por una linea verde, la clase TDialogo. Se ha colado de puntillas porque fijaos que el título que hemos utilizado es "Relaciones de Uso entre TBrowser y TRejilla" y de hecho, esta relacion entre TDialogo y TRejilla, no aparece expresamente definida en el curso de Marteens (o al menos no la he encontrado ni la recuerdo en estos momentos). Tampoco preocuparos demasiado si ahora mismo no lo veis porque mas adelante debería aparecer esta situación (la ficha de beneficiarios tenía direcciones y tambien suscripciones a actividades si recordais el esquema de nuestra bbdd). Serán necesarios unos pequeños cambios.

Siendo como es TRejilla, una clase especializada en la manipulación de datos, puede llegar a ser usada tambien para la edición de los detalles asociados al maestro de datos. Eso forma parte de la idea u objetivo deseado, que es la generación y uso de prototipos. Imaginemos la rejilla general de clientes, ¿de que nos serviremos para acceder a la ficha del cliente?. La respuesta es la que imaginais: de una instancia de TRejilla, que será la que nos lanzará el formulario de edición de registro. Pero... una vez dentro de esta: ¿por que no van  a existir otros detalles? El cliente tendrá direcciones, pero tambien catalogos, tendrá tarifas, tendra un detalle de comisionistas. Otra instancia de TRejilla puede ser usada perfectamente siguiendo el mismo patrón, creando esquemas fáciles de memorizar de la estructura de cada módulo. En ese momento uno se da cuenta de que no necesita memorizarlos sino saber si ese módulo cumple con ese esquema general, lo cual facilitá el manteniento semanas o meses despues. Cada vez que construyas un modulo que se acople al prototipo deseado, menor coste de mantenimiento a medio y largo plazo.

Aunque no siempre, uno se puede ajustar, puesto que la convivencia de un modelo basado en la cache local, como es este, puede llegar a tener que convivir con la gestión de información real, capturada a través de consultas directas y puede ser problematico que ambos modelos convivan. Así que es posible que exista un pequeño porcentaje de formularios que requieran otro prototipo diferente, lo cual hasta cierto punto resulta lógico.

Tenemos que seguir avanzando. Otro paso mas. Allá vamos Ian...

Abrir el proyecto donde lo dejamos. En el IDE de Delphi hacemos "File->New->VCL Forms application - Delphi for Win32" y llamamos a ese nuevo formulario creado como "UAncestro.pas", guardandolo en la carpeta deseada. Podemos fijar el nombre del mismo como Ancestro.

Esta es sección de interface del modulo que Ian nos propone:

unit UAncestro;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Registry, ComCtrls;

type
  IRegistroPropiedades = interface
  ['{365DAA3B-DADE-4FBD-9472-ED6F84D3B1DE}']
    procedure Leer(R: TRegIniFile; const Seccion: string);
    procedure Guardar(R: TRegIniFile; const Seccion: string);
  end;

  TAncestro = class(TForm)
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormCreate(Sender: TObject);
  private
  protected
    function Modificado: Boolean; virtual;
    function Confirmar: TModalResult; virtual;
    procedure Guardar; virtual;
    procedure Descartar; virtual;
  protected
    procedure RegistroLeer; virtual;
    procedure RegistroGuardar; virtual;
  public
    class function Ejecutar(AOwner: TComponent = nil): TModalResult;
    class function Instancia: TAncestro;
    class function Mostrar: TAncestro;
  end;

En fin... vamos a aplicar el primer recorte y le damos un tijeretazo a lo que hace referencia a la persistencia (a la interfaz IRegistroPropiedades). Tambien hemos suprimido la implementación del evento OnCreate que tampoco es imprescindible. Ahora añadimos un componente a nuestro formulario de la clase TActionList, donde se va a establecer una nueva acción que llamaremos "acFinalizar" y generamos la respuestas al evento OnExecute de la misma. Para lanzarla le he definido un Shortcut en el inspector de objetos asignando la pulsación de las teclas (Shift+Ctrl+F12). El proposito lo veremos al comentar el evento OnCloseQuery del formulario. Esto es lo que nos queda:

unit UAncestro;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Registry, ComCtrls, ActnList;

type

  TAncestro = class(TForm)
    ActionList1: TActionList;
    acFinalizar: TAction;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure acFinalizarExecute(Sender: TObject);
  private
  protected
    function Modificado: Boolean; virtual;
    function Confirmar: TModalResult; virtual;
    procedure Guardar; virtual;
    procedure Descartar; virtual;
  protected
    procedure RegistroLeer; virtual;
    procedure RegistroGuardar; virtual;
  public
    class function Ejecutar(AOwner: TComponent = nil): TModalResult;
    class function Instancia: TAncestro;
    class function Mostrar: TAncestro;
  end;

Empezamos a trabajar en la implementación del módulo donde vamos a intentar comentar los razonamientos que encierran estas funciones.

Añadimos un recurso de cadena (esto será necesario para el diálogo de la acción).

resourcestring
  SForzarCierre = 'Si sigues adelante los cambios se perderán.'#13'¿Seguir? (S/N) ';

Las tres funciones de clase definidas para TAncestro en la parte publica (Mostrar/Instancia y Ejecutar) van a ser parte de los engranajes mas importantes en la transición entre formularios. Vamos a ver la implementación y la comentamos.

class function TAncestro.Ejecutar(AOwner: TComponent): TModalResult;
begin
  if AOwner = nil then
    AOwner := Application.MainForm;
  with Create(AOwner) do
  try
    Result := ShowModal;
  finally
    Free;
  end;
end;

class function TAncestro.Instancia: TAncestro;
var
  I: Integer;
begin
  for I := Screen.FormCount - 1 downto 0 do
  begin
    TForm(Result) := Screen.Forms[I];
    if Result.ClassType = Self then Exit;
  end;
  Result := nil;
end;

class function TAncestro.Mostrar: TAncestro;
begin
  Result := Instancia;
  if Assigned(Result) then
  begin
    if Result.WindowState = wsMinimized then
      ShowWindow(Result.Handle, SW_RESTORE);
    Result.BringToFront;
  end
  else
  begin
    Result := Create(Application.MainForm);
    Result.Show;
  end;
end;

Todo empieza cuando la ventana principal llama a la función de clase Mostrar. Pero ¿Quien llama a mostrar? Lo mejor es que lo veamos con la implementación de la acción que nos permite acceder al modulo de beneficiarios. 

procedure TPrincipal.VerBeneficiariosExecute(Sender: TObject);
begin
  TBeneficiarios.Mostrar;
end;

¡Vale!  TBeneficiarios es un de las clases que va a descender de TBrowser y dado que puede ejecutar el metodo definido en su ascendente, el resultado va a ser como sigue: primero se evalua la llamada a Instancia que va a devolvernos si la ventana ya se encuentra en ejecución (fijaros como Ian recorre todos los formularios de la matriz Screen para saber si ya se encuentra activo). Y depende del valor de esta referencia (si es nil o se encuentra asigndada) decidirá entre crear o reactivar el formulario respectivamente.

El resultado final es que tenemos un mecanismo comun a todos los formularios que sean descendientes de nuestro TBrowser, que van a ser la clase que nos muestre la rejilla de datos de trabajo.

 

Y con respecto a la función de clase Ejecutar, nos permitirá invocar la creación de los descendientes de TDialogo, que serán las fichas o formularios en los que el usuario podrá modificar o insertar registros. La clave es entender que la llamada a Create se ejecutará en el contexto de la clase descendiente, por lo que el resultado final es la creación del formulario y el consiguiente lanzamiento modal.

Para finalizar vamos a ver la implementación del bloque de procedimientos que permiten a Ian Marteens decidir que hacer con los cambios que ha hecho el usuario. Daros cuenta como el nivel de abstracción es grande. Se establecen las lineas de que va a hacer en cada caso pero no se preocupa a este nivel de su definición. Precisamente serán las clases descendientes las que tengan la responsabilidad de dar contenido a los procedimientos virtuales como Guardar o Descartar.

Quedaría como sigue:

procedure TAncestro.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

procedure TAncestro.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  case ModalResult of
    mrNone:
      if Modificado then
        case Confirmar of
          mrYes: Guardar;
          mrCancel: CanClose := False;
        else
          Descartar;
        end;
    mrOk:
      Guardar;
    mrAbort: ;
  else
    if not Modificado or (Confirmar = mrYes) then
      Descartar
    else
      CanClose := False;
  end;
end;

procedure TAncestro.acFinalizarExecute(Sender: TObject);
begin
  if MessageDlg(SForzarCierre, mtWarning, [mbOk, mbCancel], 0, mbCancel) = mrOk then begin
     ModalResult:= mrAbort;
  end;
end;

function TAncestro.Confirmar: TModalResult;
var
  Mensaje: string;
  Botones: TMsgDlgButtons;
begin
  if ModalResult = mrNone then
  begin
    Mensaje := '¿Desea guardar los cambios?';
    Botones := mbYesNoCancel;
  end
  else
  begin
    Mensaje := '¿Desea abandonar los cambios?';
    Botones := [mbYes, mbNo];
  end;
  Result := MessageDlg(Mensaje, mtConfirmation, Botones, 0);
end;

function TAncestro.Modificado: Boolean;
begin
  Result := False;
end;

procedure TAncestro.Guardar;
begin
end;

procedure TAncestro.Descartar;
begin
end;

end.

No se si estais de acuerdo conmigo, pero con cuatro lineas Marteens ha definido genialmente lo que hubieran podido ser tediosas repeticiones en cada uno de los modulos creados, de no haber utilizado este tipo de prototipos. Creo que cualquier programador disfruta cuando encuentra en su camino código que le hace pensar como hacer mejor las cosas y este módulo es un buen ejemplo de ello, sobretodo por su sencillez.

Nuestro usuario hizo doble click sobre la rejilla, o lanzó la ejecución de las acciones Editar o Insertar de menús o botones, y tras modificar los valores en el formulario, llega el momento inevitable en el que tiene que confirmar o descartar cambios.  ¿Hacia donde pues deberiamos mirar para entender está pasando en ese bloque de código? Parece que la clave es evaluar la respuesta del usuario (saber si ha aceptado los cambios y bien los ha rechazado) y en función de esta respuesta actuar.

Eso sí, ese actuar, va condicionandose en funcion tambien de que existan modificaciones o no (nos lo diría la llamada a Modificado) ya que no es lo mismo que el usuario haya pulsado aceptar o descartar cambios. Tambien se previene que el usuario accidentalmente pueda perder los cambios hechos cancelando de forma fortuita, momento en el que un dialogo modal debería rescatarlo y darle la posibilidad de salvarlos adecuadamente.

Con respecto a la acción Finalizar, que no figura inicialmente en el framework, se corresponde con una "necesidad" descubierta durante el desarrollo real. Vereis, siempre partimos de situaciones felices en las que prevenimos los problemas que mas o menos vislumbramos en la etapa de analisis. En algunas ocasiones, si se producía una excepción durante el proceso de cierre de la ventana modal, era posible que esta no pudiera ser cerrada, ya que el usuario ni podía confirmar los cambios ni descartarlos. En una situación donde no existía forma de escapar, suponía un pequeño truco útil para que el usuario pudiera escapar del nudo, sabiendo que serían descartados los cambios hechos en la edición de la ficha, como mal menor. Por esa razón se reserva el tratamiento de la respuesta Abort que escapa y permite cerrar el formulario y descartar los cambios. Pero esto habría que verlo desde el enfoque de una solución de urgencia  y no como un tratamiento a la excepción. De hecho, en una aplicación real podría existir un mecanismo de comunicación con el departamento de desarrollo para recibir esta incidencia  (via mail, via log) y tratarla adecuadamente en una versión posterior.

Hasta aquí nos ha llevado el comentario de la clase TAncestro. El siguiente peldaño nos llevaría a estudiar las dos clases que descienden directamente de ésta, en la relación de jerarquía: TBrowser y TDialogo. Además, va a ser una buena excusa para ver de cerca otro detalle muy interesante, que son las interfaces. Seguimos en la primera semana del próximo mes.

Los que podais estar interesados en el curso de Marteens sabeis que podeis encontrar información pulsando en:

http://www.marteens.com/

  Siempre repito (creo que debo comentarlo al final de cada entrada de la serie) que estas entradas son meramente comentarios de un programador que ha seguido en su trabajo uno de los frameworks de los cursos y que está compartiendo esa experiencia con la comunidad. A mi, personalmente, me ha parecido fantástico y lo tengo en una valoración muy alta. No existe la intención de recrear todo el framework sino simplemente comentar los puntos que me parecen claves o interesantes del mismo, aunque logicamente, para poder verlos, le pedí permiso previamente a Ian, que no puso objeción, tal y como comenté en la primera de las entradas.

 Hago y recalco este comentario para que no haya quien crea que Marteens participa de algun modo conociendo el contenido de estas entradas o dando el visto bueno a lo que voy escribiendo. En todo caso, yo como muestra de agradecimiento, dado que estas páginas no tienen caracter lucrativo sino todo lo contrario y es la unica forma que tengo de agradecercelo, estoy incluyendo al final un enlace a los cursos, con independencia de que piense que es un dinero bien gastado y se lo pueda recomendar abiertamente a los programadores que se inician en Delphi. Pero tampoco es algo que me haya pedido el, porque entre otras cosas tampoco lo necesita.

Hay muchos libros y documentación en los que se puede conocer la sintaxis del lenguaje o el uso de tal o cual componente o de tal o cual estructura... pero prácticamente ninguno donde puedas ver de cerca el desarrollo de gestión de datos con el detalle con el que Marteens los aborda.

Nada mas. Espero que pueda ayudar a alguien todos estos comentarios.

Etiquetas: Delphi, frameworkm delphi datasnap midas, Ian Marteens, TAncestro, TBrowser, TDialogo, TRejilla
Comentarios desactivados



A cerca de mí



Introduce tu correo electrónico y suscríbete al blog, y recibe así notificaciones de nuevos posts por correo. Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 1 other subscriber

¿Quién es Embarcadero y su hoja de ruta? (28/10/2010)


Suscríbete al canal tecnológico multimedia de Danysoft

Etiquetas

Albor Andreano Lanusse Boletin clases Codegear comunidad Comunidad Velneo Control Versiones Código Danysoft datasnap david intersimone Delphi Delphi 2007 Delphi 2009 delphi 2010 delphi prism Delphi XE Embarcadero Firebird framework Freire Ian Marteens Jedi VCL Madrid Marco Cantú Neftali Noticias Novedades ODBC oop presentacion Rad studio rad studio 2010 rad studio xe RadStudio XE2 Recursos Seminario web Sintesis twitter Velneo Video Videos Web WordPress

Google + Grupo Delphi Solidario


Delphi Solidario G+
on Google+

Twitter @salvadorjover

  • An Interface Implementation Pattern for Consideration: http://t.co/PtehB8ue via @AddThis 1 day ago
  • Flotsam and Jetsam #54: http://t.co/2ddkTXum via @AddThis 1 week ago
  • El misterio de los 200… http://t.co/boQw57XT 1 week ago
  • El misterio de los 200... - http://t.co/llwdf7vU (via @sociablesite) 1 week ago
  • Una semana histórica contra el Copyright http://t.co/Kq1Y3cEa vía @wordpressdotcom 1 week ago

Comentarios recientes

  • Salvador Jover en El misterio de los 200…
  • Markos en Mi método doble valor y expresiones con LiveBindigns
  • Germán Estévez en Feliz Navidad :-)
  • salvador en Feliz Navidad :-)

RSS Stack Overflow – Tag (Delphi)

  • Apache 2.2 module febrero 6, 2012 Dorin Duminica
  • Loading StringGrid from text file febrero 5, 2012 yoahim
  • I dont see "Export Xcode" in delphi xe2...? febrero 5, 2012 hossein salimi
  • why does a thread sometimes hang in waitfor? febrero 5, 2012 Arnold
  • Objective-c: read file encrypted on Windows febrero 5, 2012 Kevin McBrearty
  • Numbering ListView febrero 5, 2012 qwertaz
  • Integer() type cast doesn't work on Delphi 64-bit febrero 5, 2012 user1190891
  • Why does TStringList have BeginUpdate and EndUpdate? febrero 5, 2012 Blobby

RSS Embarcadero: Ultimas entradas

  • Upgrade from any earlier version offer extended febrero 5, 2012 Tim
  • XE2 VCL Styles "EQ" febrero 3, 2012 Michael Swindell
  • Upcoming webinars for Delphi developers febrero 3, 2012 Tim
  • FireMonkey 3D Text Editor febrero 3, 2012 Andreano Lanusse
  • Wireless Android Control febrero 3, 2012 RemObjects Delphi Prism
  • Visualizing physics using FireMonkey - Game of Billiards! febrero 2, 2012 Anders Ohlsson
  • Problems installing and/or launching Android apps (2) febrero 2, 2012 RemObjects Delphi Prism
  • Jan 2012 Release of Oxygene febrero 2, 2012 RemObjects Delphi Prism

Dar un vistazo a las ofertas de trabajo…


¿Habeis visitado el enlace del Club Delphi sobre ofertas de trabajo a programadores de Delphi?

Ofertas de trabajo


Comunidad DevExpress

  • DevExpress
  • DevExpress Blogs
  • Online Demos
  • Woody Pewitt – DevExpress Analytics Blog

DBOptimizer

  • DB Optimizer

delphi prism

  • MarkDelphi: El blog de programacion con Delphi Prism y .net

Delphi: Bitácoras

  • Andreano Lannuse blog: Making things passing
  • Blog de Andreano Lanusse
  • Blog de Antonio Galicia
  • Blog de José Castillo
  • Blog de Salvador Gómez
  • Carlos Garcia Trujillo
  • Comma NET
  • Daniel Luyo
  • Delphi Magic
  • Delphi-Neftalí
  • El blog de jachguate
  • El blog de Johnny Suarez: El mundo tecnológico, en especial el mundo Delphi y Firebird SQL en un solo lugar
  • La turbo señal
  • La web de JM
  • La web de JM -Repositorio de código-
  • Leonardo's blog
  • MarkDelphi: El blog de programacion con Delphi Prism y .net
  • Rescatando a Delphi
  • Waooo Delphi!

Delphi: Canal Video

  • Chanel E (Embarcadero)

Delphi: Comunidad Brasil/Portugal

  • Cesar Romero – Delphi programming
  • Delphi for all
  • Delphi: Blog somente para usuarios de delphi ALL, Firebird e SQL..
  • Facebook – Delphi Brasil
  • Joao Morais -Object Pascal e ferramentas de programação com Arte-
  • Planeta Delphi
  • Programmer Object Pascal
  • Vitor Rubio {developer} -Dicas de Delphi, C#, Javascript, etc-
  • [D2D] Delphi to Delphi – Tecnologia hoje e sempre

Delphi: Comunidad Francesa

  • Le blog de John COLIBRI

Delphi: Comunidad Rusa

  • Delphi e Internet

Delphi: Enlaces

  • Calling Dr. Marteens
  • Club Delphi
  • Delphi Heaven
  • El Rinconcito de Delphi
  • Electronic Document Library
  • Firebird en español
  • Francisco Charte Ojeda
  • Grupo Albor
  • Latium Software
  • Trucomanía

Delphi: General

  • http://www.danysoft.com/ http://www.danysoft.com/
  • http://www.embarcadero.com http://www.embarcadero.com

Delphi: Recursos No hispanos

  • About.com: Delphi programming
  • Andy’s Blog and Tools – Delphi, C++Builder and other thoughts
  • Blaise Pascal Magazine
  • Blog de Delphidabbler
  • Brian Long Consultancy & Training Services
  • Chau Chee Yang Technical Blog
  • Chris Bensen
  • CODEGEARGURU.COM – Making You the Guru
  • Delphi Basics
  • Delphi For Fun
  • Delphi Haven
  • DelphiDalbler.com
  • DelphiInsider
  • Dr.Bob's Delphi Programming Clinic – for Delphi (for .NET), Kylix, and C++Builder
  • ENTROPY OVERLOAD
  • Hunters Vulnerability
  • JEDI Windows API
  • Lennie De Villiers Blog
  • Marco Cantú (Wintech Italia) Digital Development
  • marco's tech world
  • Marius Zemaiti's Blog
  • Michael Rozlog
  • Nick Hodges
  • Nick Hodges: A man's got to know his limitations
  • Pawel Glowacki: Delphi Programming
  • Ramblings -Delphi Programming Observations-
  • Random thoughts on coding & technology
  • Reinvent the wheel
  • Rudy's Delphi Corner
  • Software Development. -Delphi Programming and software in general-
  • Source Code Adventures
  • The Delphi Geek
  • The Podcast at Delphi.org
  • VCL DEVELOPER
  • while true do; Daniele Teti’s programming blog

Demos y Código fuente

  • Demos de Rad Studio XE2

Docwiky Embarcadero

  • Docwiki Embarcadero (Rad Studio)

Foros

  • Delphi Access Delphi Access

Intraweb

  • Blog oficial de Intraweb
  • Pagina oficial de Intraweb
  • Twitter de Intraweb
  • Zona descargas

Modelado BBDD

  • Database Answers – Data Models

Otras materias

  • Design Patterns

SQL Server

  • Guille SQL: Un portal sobre Microsoft SQL Server en castellano

Usuarios Conectados


Aviso legal - Privacidad de datos
  • Mapa de entradas

      El misterio de los 200…
      Mi método doble valor y [...]
      Feliz Navidad :-)
      Videos demostrativos [...]
      Ultima promoción de Danysoft [...]
      Testeando XE2 (FastReport)
      Conversando con Pawel [...]
      Delphi Básico, Delphi [...]
      Barcelona y RadStudio XE2: [...]
      Una imagen vale más que mil [...]
      Aviso sobre el grupo de [...]
      ¡Felicidades Septiembre!
      Vacaciones…
      Otras mandangas…
      La web de JM
      Cerrando temas…
      Expresiones regulares: [...]
      Expresiones regulares: [...]
      Expresiones regulares: [...]
      Preliminar Delphi 64 (Video [...]
      Trabaja en equipo con XE
      Presentación de la [...]
      Expresiones regulares: [...]
      Oferta promoción XE marzo
      El mundo al revés
      New hotfixes available for [...]
      Iniciamos febrero con buen [...]
      Delphi Starter Edition, ya [...]
      ¡Feliz Navidad! :-)
      Nos deja la semana 51 – [...]
      TPanelMiniaturas (primera [...]
      2º Aniversario de [...]
      A tener en cuenta…
      Delphi and RAD Studio Install [...]
      Panel de miniaturas
      Más sobre RAD Studio XE…
      Presentación Rad Studio XE [...]
      Nos deja la semana 37 – [...]
      Delphi For Android Sneak [...]
      Nuestra hoja de ruta… (II)
      Módulo de control de [...]
      ¡Ruido de campanas!
      No me saques los colores
      RAD Studio XE: Mejoras sobre [...]
      Nuestra hoja de ruta…
      Actualización de wordpress
      El pulpo PAUL no [...]
      TDBLookupComboBox al [...]
      Blaise Pascal Magazine Nº 11 [...]
      ER/Studio Developer Edition y [...]
      Plan de ruta de Embarcadero [...]
      Últimas promociones de [...]
      Ultimas noticias sobre Delphi [...]
      La gran división…
      RAD Studio 2010 Hotfix 2
      Failover Server in DataSnap [...]
      Secreto de tres, secreto no [...]
      Simplifica tu código [...]
      Seguir la linea…
      Shifting TFields in TDataSets [...]
      Delphi 2010 Handbook With [...]
      Eventos gratuitos del mes de [...]
      Un día con los mayores (5) y [...]
      Semana intensa
      Próxima cita el 26 de [...]
      Channel E (Embarcadero) [...]
      Delphi Prism (Manual de [...]
      Lo que nos deja la [...]
      Más recursos…
      Un día con los mayores (5) [...]
      Haití necesita de tu [...]
      Ampliado el plazo de la [...]
      ¡Feliz Navidad!
      Release Notes: Delphi and [...]
      Ultimos videos de [...]
      Colección de recursos [...]
      Manifiesto en defensa de los [...]
      I need educated… [...]
      Touch demo… esperada
      Perfil humano y comunidad de [...]
      Conéctame a…
      Un día con los mayores (4)
      El update fantasma…
      Próximos eventos en España [...]
      …On the Wings of the [...]
      Un apunte sobre Jedi VCS y [...]
      De la mano del Dr. Bob
      Sobre las RXLib [...]
      Añadida una nueva sección [...]
      Al final del mes de Octubre o [...]
      Vamos, vamos… no hay [...]
      Un día con los mayores (3)
      Ultima oportunidad para [...]
      Enlace a las novedades y [...]
      Esos dichosos métodos [...]
      Un día con los mayores (2)
      ¿Alguien me lo [...]
      Consumir un web service [...]
      JSon y el vellocino de [...]
      Disponibles los videos del [...]
      Fin de la promoción: [...]
      Seminario Web de 18 de [...]
      Una encuesta simpática
      Disponibles para visionar las [...]
      ¿De verdad que te lo vas a [...]
      Olvidaba…
      Un día con los mayores (1)
      Un ojo mirando a la [...]
      Por llegar: video de [...]
      Cosas y casos…
      Una comparación entre Delphi [...]
      Próximas entradas… [...]
      Un poco de todo…
      ¿Ya se puede ver la página? [...]
      Valoración de Quality [...]
      El futuro de Delphi parece [...]
      Un pequeño resumen, por [...]
      El mundo en un [...]
      Lo mas destacado… :-)
      Experimentos, reflexiones y [...]
      Cerrando la semana
      Experimentos, reflexiones y [...]
      Curioseando…
      Experimentos, reflexiones y [...]
      Nuevo Twibe delphiespanol: [...]
      Nos vamos de visita a…
      Experimentos, reflexiones y [...]
      Tribus y comunidades…
      Experimentos, reflexiones y [...]
      DBA Day Online (7 de Mayo de [...]
      Cerrando temas…
      Actualizacion de WordPress
      Por curiosidad (y Parte III)
      Por curiosidad (Parte II)
      Por curiosidad (Parte I)
      Cero contra quinientos [...]
      Lo que no es programar
      Cero contra quinientos [...]
      Cero contra quinientos [...]
      Casi desde el techo de [...]
      La proxima entrada…
      Pronto para valorar…
      Presentación Delphi 2009
      JEDI Version Control System [...]
      JEDI Version Control System [...]
      JEDI Version Control System [...]
      Recursos delphi… una [...]
      JEDI Version Control System [...]
      Problemas de visualización [...]
      ¿Juegas conmigo?
      Algunos cambios…
      Delphi 2009 Handbook – [...]
      Siete vidas tiene el [...]
      Algunas ventajas… (¡si [...]
      Comentario [...]
      Embarcadero Technologies® [...]
      Algunas ventajas… (¡si [...]
      Algunas ventajas… (¡si [...]
      Enlace interesante…
      Relacion de Boletines [...]
      Ya queda menos…
      Pequeño parón…
      CodeGear provee 1 millón de [...]
      Jugar al escondite…
      No te olvides…
      ¿Sabías que…
      Atrévete a conocer Velneo [...]
      Lo último que he leído
      Atrévete a conocer Velneo [...]
      Dos artículos interesantes [...]
      Delphi 2007 HandBook – [...]
      Relación de boletines [...]
      ¿Hacemos un trío…?
      Nuevo tema de WordPress para [...]
      Próxima salida del [...]
      Codegear y las jornadas [...]
      Pescando en la red…
      Enlace interesante sobre [...]
      ¿Puedo ser más [...]
      Un enfoque modular para [...]
      Domk no pierde [...]
      Enlace interesante…
      De sabios es [...]
      CodeGear Rad Studio [...]
      A vueltas con la plantilla [...]
      Empezar la casa por los [...]
      La paciencia…
      Casualidades…
      ¿Quién no tiene [...]
      Seleccionar una carpeta
      Solidaridad con las víctimas [...]
      Algunos tutoriales y cursos [...]
      La Granja
      ¿Cerrado por [...]
      Cerrada encuesta Delphi: [...]
      Incorporado a la VCL a partir [...]
      A mitad de camino…
      Tres en Raya para Velneo
      Actualización…
      Retomar temas [...]
      Empezar por un juego…
      Añadida encuesta sobre [...]
      Convenciones de llamada
      Primeros pasos tras el [...]
      Bienvenidos a Velneo desde [...]
      Tomar la vida un poco menos [...]
      Acabando tareas pendientes
      Obras son amores…
      Boletín extraordinario: [...]
      Comparaciones: Delphi VS [...]
      Comparaciones: Delphi VS [...]
      Comparaciones: Delphi VS [...]
      Presentacion de Delphi 2007
      Sobre ModelMaker
      ¿Te topaste con un [...]
      ¿Te topaste con un [...]
      Mas off topic…
      Off topic…
      Sobre ModelMaker (segundo [...]
      Sobre ModelMaker
      Más sobre el tipo PChar
      Despiste…
      El cortador de líneas
      Sobre el creador de Delphi
      Sobre el artículo del [...]
      Una etiqueta más util…
      Cambio de tema…
      Dime algo sobre [...]
      Los foros…
      El traje del emperador…
      Que hago si…
      Nuevo boletín
      No perder de vista…
      ¿Enlace util…?
      Iniciando el 2006…
      Modales por favor…
      TShellTreeView
      Multifind
      TThreads
      Objetos auxiliares
      Sobre QuickReport
      Antes de Albor

 
 
 

Delphi básico

  • Registrarse
  • Acceder
  • RSS de las entradas
  • RSS de los comentarios
  • WordPress.org

Pensamientos cristianos


Dios habla a los hombres a través de esa belleza única llamada María", Madre de Dios y Madre nuestra.

SS. Juan Pablo II

Soporte

Descargar TeamViewer QuickSupport Descargar TeamViewer

Spam

345
SPAM BLOCKED

Translate with Google



Delphi Básico funciona basado en WordPress and BuddyPress. Just another WordPress Theme developed by Themekraft.