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.
[Delphi] unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TFrame1 = class(TFrame) private { Private declarations } public { Public declarations } end; implementation {$R *.dfm} end. [/Delphi]
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.
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.
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.
[DELPHI]
unit UGrids; interface uses Windows, Messages, Classes, SysUtils, Graphics, Controls, Grids, DBGrids, DB, DBClient, UAncestro; const CM_MOSTRAREDITOR = WM_USER + 1; type TDragControlObjectExClass = class of TDragControlObjectEx; TDBGrid = class(DBGrids.TDBGrid) protected procedure DblClick; override; procedure TitleClick(Column: TColumn); override; public end; implementation { TDBGrid } procedure TDBGrid.DblClick; begin inherited DblClick; if Assigned(Parent) and Parent.HandleAllocated then PostMessage(Parent.Handle, CM_MOSTRAREDITOR, 0, 0); end; procedure TDBGrid.TitleClick(Column: TColumn); var S: string; begin try S := Column.FieldName; with DataSource.DataSet as TClientDataSet do if IndexFieldNames <> S then IndexFieldNames := S else begin AddIndex(S, S, [], S); IndexName := S; end; except end; inherited TitleClick(Column); end; end.
[/DELPHI]
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.
[DELPHI]
unit URejilla; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DBActns, ActnList, DB, DBClient, Grids, DBGrids, Menus, Contnrs, UAncestro, UDialogo, UDatos, Printers, UGrids; type IManipulacionDatos = interface ['{DCCCAB14-F5F8-49C5-8E95-E2E1E8A79749}'] function Insertar(D: TClientDataSet): Boolean; function Eliminar(D: TClientDataSet): Boolean; function Modificar(D: TClientDataSet): Boolean; end; TGeneradorSQL = function : string of object; TRejilla = class(TFrame, IManipulacionDatos) ... protected FUltimaBusqueda: TMenuItem; FEditor: TDialogoClass; FDetalles: TClientDataSet; function ManipuladorDatos: IManipulacionDatos; procedure CMMostrarEditor(var M: TMessage); message CM_MOSTRAREDITOR; // IManipulacionDatos function Insertar(D: TClientDataSet): Boolean; function Eliminar(D: TClientDataSet): Boolean; function Modificar(D: TClientDataSet): Boolean; property Grid: TDBGrid read DBGrid; public destructor Destroy; override; procedure Abrir(Generador: TGeneradorSQL = nil); function AbrirDetalles(Insertar: Boolean): TClientDataSet; procedure CerrarDetalles; property Detalles: TClientDataSet read FDetalles write FDetalles; property Editor: TDialogoClass read FEditor write FEditor; end;
[/Delphi]
¿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.
Hello, I thinκ your blog might be haѵing browѕer compatіbility issues.
When I lоok at your blog іn Opеrа,
it lοоκs fіne but when opеning in Internet Explоrer, it has ѕome overlapping.
I just wanted to give you a quick heads up! Other then thаt, wonderful blog!
Me gustaMe gusta