¡Dar una buena imagen! Parte I

Hace unas semanas, compartía con uno de los compañeros de la Comunidad su inquietud hacia  una de las entradas que se publicó al final del año anterior. Concretamente este compañero me hablaba de la entrada del Seminario de Valencia, en cuyo interior se incluían algún retazo del código que se vio en aquella sesión.

Según me comentaba, el intento de obtener una imagen desde un servidor de DataSnap y mostrarla en la parte cliente, era lo que en ese momento le estaba dando problemas, por lo que, tras cruzar varios correos con él, decidí preparar un pequeño ejemplo en el que lo pudiera ver y entender con algo mas de detalle, tomando como guía aquella entrada del seminario.

Así que estas lineas responden un poco a esa motivación, pensando que otros compañeros puedan albergar dudas similares y pueda ser de ayuda su lectura.

Divide y vencerás

Al igual que reza la máxima clásica, he creído conveniente dividir el contenido en varias partes para que queden un poco mas claros los comentarios que vamos a compartir. La idea es muy simple: crear un servidor de DataSnap con un método que devuelva una imagen y que sea consumida por una aplicación cliente. Esa era la pregunta inicial que él me hacía y la idea -pienso yo- es que sea lo mas sencillo posible para no enredar con otros puntos que puedan desviarnos. Lógicamente, y para poder ver esto, necesitamos previamente que existan esas imágenes y una base de datos de forma que el servidor de datos acceda a este contenido y lo exporte al cliente que consume el servicio.

Por todo esto, en esta primera parte vamos a presentar la base de datos que servirá a nuestro propósito  la cual será la que originalmente contiene las imagenes que el servidor de datasnap exportará. Y construiremos una pequeña utilidad o herramienta “casera” que nos permita acceder a la tabla de imágenes para gestionar su contenido. De esa forma, al acabar esta parte, tendremos todo a punto para abordar la creación del servidor y cliente.

A continuación podéis ver unas imagenes de la aplicación:

en_ejecucion

 

en_ejecucion

En la segunda parte, que será publicada posteriormente, completaremos el ejercicio o la demo, o como queráis llamarlo, con la creación del servidor y cliente de datasnap (que usará la base de datos que ahora vamos a ver).

Finalmente, una tercera parte abordaría un tema más avanzado, en el que nos preguntamos qué hacer si necesitamos exportar un tipo complejo, fuera de los que existen por defecto.

Ahh. Olvidaba comentar que nos apoyaremos en Rad Studio XE3 e Interbase, a través de la plataforma de FireMonkey.

 Nuestro planteamiento…

Lo primero de todo, vamos a crear la base de datos, en la que por simplicidad, se definirá una sola tabla que supuestamente albergará las imágenes que nos servirán a efecto de banner, razón por la que he acabado llamado a la tabla con ese nombre (Banners). Adicionalmente, vamos a definir dos campos en dicha tabla: IDBanner (Integer) y Banner (Blob), que como podéis observar, representan respectivamente la clave primaria y el campo que contiene la imagen. Se supone que al final, quedaría una tabla conteniendo en sus respectivos registros las distintas imágenes que exhibiriamos como “publicidad” de forma que pudiéramos hacer algo con ellos (podríamos por ejemplo desear mostrar aleatoriamente uno de ellos, o automatizar transiciones mediante llamadas periodicas, etc..). Digo esto mas que nada para que tenga un poco de sentido el ejemplo.

En nuestro script de Interbase, se representa con la siguiente sentencia ddl:

CREATE TABLE BANNERS (
    IDBANNER  INTEGER NOT NULL,
    BANNER    BLOB SUB_TYPE 0 SEGMENT SIZE 80
);

Es obvio que no es lo habitual. En la vida real hubiéramos considerado guardar la ruta del fichero y no su contenido, ¡estamos de acuerdo! pero el hecho de que precisamente no fuera ésto lo habitual y el hecho también de que ambos supuestos sean asimilables de una forma sencilla, a efectos de nuestro código, me hizo valorar que fuera mas conveniente y didáctico usar el tipo blob, y guardar el contenido de la imagen en formato binario.

A fin de cuentas, desde nuestro servidor de datos, el código que forma parte de ese método que devuelve la imagen, puede ser modificado fácilmente, considerando un caso u otro. Mas abajo podéis ver unas lineas forman parte de ese código que veremos en la parte II. Considerad por ejemplo, en dichas lineas, el uso de LoadFromFile( ) en lugar de WriteData( ), si es que conocemos la ruta en la que está ubicada la imagen y que previamente hemos leído en la bbdd de un supuesto campo Ruta (varchar), en el que podríamos tener guardada la ruta de cada imagen.

...
  stream := TMemoryStream.Create;
  stream.WriteData(ASQLQuery.FieldByName('Banner').AsBytes,
                   Length(ASQLQuery.FieldByName('Banner').AsBytes));
  stream.Seek(0, System.Classes.TSeekOrigin.soBeginning);
...

Lo veremos en esa segunda parte.

En el ejemplo, nuestra base de datos se llama DEMOS.GDB y la tenéis ubicada en el directorio BBDD del código fuente. No obstante, por si existe quien desee probarlo en sql server en lugar de Interbase, he incluido ambos scripts, y podemos con un pequeño ajuste de los parámetros de conexión de datos apuntar hacia el respectivo motor. son pocos los cambios que habría que hacer y por ello lo dejo a criterio vuestro.

Gestionar el contenido

Vamos a considerar solo dos operaciones básicas: insertar una imagen y eliminarla, ya que no existen campos que contengan detalles de la imagen, ni título ni información asociada a la misma y no tiene demasiado sentido una operación para actualizar. ¡No somos chulos ni na…!

🙂

Lo haremos de la siguiente forma. Vamos a crear dos procedimientos en la base de datos: uno que nos permita eliminar una imagen (a través de su indice) e otro que nos permita insertarla. Para añadir una imagen no nos es necesario un índice, considerando que esté indice se va a crear como campo autoincremento y no es un campo “editable”, pero sí que necesitamos obtenerlo a posterior, si deseamos situarnos en la posición de registro correcta.

Si buscáis en el script citado anteriormente, se corresponden con las lineas, respectivamente.

Eliminar una imagen:

ALTER PROCEDURE ELIMINARIMAGEN (
    IDBANNER INTEGER)
AS
begin
  delete from Banners where IDBanner = :IDBanner;
  suspend;
end^

Insertar una imagen:

ALTER PROCEDURE INSERTAIMAGEN (
    BANNER BLOB SUB_TYPE 0 SEGMENT SIZE 80)
RETURNS (
    IDBANNER INTEGER)
AS
begin
  idbanner = gen_id(gen_banners_id,1);
  insert into banners (idbanner, banner)
  values (:idbanner, :banner);
  suspend;
end^

El procedimiento que elimina la imagen recibe como parámetro el indice (y clave primaria) de la tabla. No hace falta comentar mucho mas. El procedure que inserta, obtiene previamente el valor del indice que va a asignar el generador y lo utiliza como valor de retorno tras la inserción. Ese generador garantiza la condición del indice de clave primaria en Interbase (o Firebird)

En el caso de sqlserver es similar aunque no exactamente igual, dado que sqlserver considera de solo lectura la clave autoincremento y por ello habilita @@Identity() que nos devuelve el valor tras la inserción, de forma que también puede ser devuelto en el parámetro @ID.

CREATE PROCEDURE dbo.InsertaImagen
@Banner IMAGE,
@ID Integer OUT
AS
BEGIN
  INSERT INTO 
    dbo.banners
  (
    Banner
  ) 
  VALUES (
    @Banner
  )   
  Set @ID = @@IDENTITY
END
GO

En este punto y ya comentado todo esto, habéis podido optar, o bien por recrear la base de datos con los scripts, sea con uno o con otro, o bien utilizar directamente el fichero ya creado en el código fuente. Sea cual sea vuestra decisión, podemos pasar al entorno y empezar a ver el código de la aplicación. Partimos de que el servicio de la bbdd está en ejecución.

Descarga código fuente

 Diseñar con el nuevo editor de LiveBindings…

A partir de aquí, y tras descargar y descomprimir el fichero zip, podéis ya en el entorno de Delphi XE3 abrir el grupo del proyecto, db_imgdemo.groupproj, dentro de la carpeta imagenesdatasnap, que contendrá finalmente todos los proyectos, incluido el servidor y el cliente. Ahora solo incluye este proyecto para no liaros.

formulario

En el formulario se han añadido los controles mínimos para gestionar el contenido de la tabla (ni siquiera he creado un datamodule para albergar los componentes de conexion a datos que viene a ser lo habitual). Un componente TImage que visualizará la imagen, una navegador (TBindNavigator) que nos permita desplazarnos en los distintos registros y un par de botones para ejecutar los métodos que insertarán o eliminarán las imágenes. También hacemos uso de acciones, para controlar que sea accesible el método que elimina la imagen, en función de que existan registros en la tabla. El componente TComboBox, realmente no hace nada. Lo he dejado por si optais por añadir ambos servicios, ya que en un primer momento, testeaba el primer codigo que habia escrito en ambos servidores seleccionando la conexion adecuada con el evento OnChange( ) del componente. Finalmente suprimí dicho código para que fuera mas claro el ejemplo. Es decir, nos podría servir para cambiar la conexión y apuntar al servidor adecuado (ejecutando también los métodos que correspondan ya que puede ser ligeramente distinta la parametrización y su índice nos puede ayudar en otro punto del programa a discrimar que debo hacer en cada caso).

Hago una observación para que no pase inadvertida para quien se inicia o lleva poco tiempo. Con este planteamiento (usar procedures), no me ha hecho falta confirmar los cambios sino que nos limitaremos a refrescar el contenido del dataset (tras la ejecución del oportuno procedimiento).

Respecto al modo en el que los distintos controles se enlazan a la fuente de datos, nos basta abrir el diseñador de relaciones de LiveBindings y veremos que es muy sencillo crear estas relaciones. Algo tan sencillo como arrastrar entre las entidades que nos muestra el editor (cuando sean compatibles). Lo resalto porque se ha mejorado mucho y media un abismo desde aquel primer editor de relaciones que nos obligaba a configurar manualmente las equivalencias y el sentido de asignación. Ahora mismo, con XE3, bastan un par de clicks (aunque sigue existiendo la posibilidad de extender el uso y crear relaciones complejas).

Veamos como quedó el editor tras hacer las conexiones

livebindings_designer

 

Un poco de código

Podéis ejecutar el proyecto (F9) para ver nuestra rudimentaria utilidad en funcionamiento y comentamos.

en_ejecucion

Este es el código de la unidad:

unit UMain;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs,
  Data.DBXMSSQL, Data.Bind.EngExt, Fmx.Bind.DBEngExt, System.Bindings.Outputs,
  Fmx.Bind.Editors, Data.Bind.Components, Data.Bind.DBScope, Data.DB,
  FMX.Layouts, Fmx.Bind.Navigator, Datasnap.DBClient, SimpleDS, FMX.Objects,
  Data.SqlExpr, System.Actions, FMX.ActnList, Data.FMTBcd, Data.DBXInterBase,
  FMX.ListBox;

type
  TfrmBanners = class(TForm)
    Image1: TImage;
    banners: TSimpleDataSet;
    bannersIDBanner: TIntegerField;
    Label1: TLabel;
    BindSourceDB1: TBindSourceDB;
    BindingsList1: TBindingsList;
    LinkPropertyToField1: TLinkPropertyToField;
    OpenDialog1: TOpenDialog;
    acciones: TActionList;
    Button1: TButton;
    acInsertarImagen: TAction;
    BindNavigator1: TBindNavigator;
    acEliminarImagen: TAction;
    Button2: TButton;
    LinkPropertyToField2: TLinkPropertyToField;
    bannersBanner: TBlobField;
    cbxSelector: TComboBox;
    ListBoxItem1: TListBoxItem;
    ListBoxItem2: TListBoxItem;
    Label2: TLabel;
    demos_Interbase: TSQLConnection;
    spInsertarImagen_Interbase: TSQLStoredProc;
    spEliminarImagen_Interbase: TSQLStoredProc;
    procedure FormCreate(Sender: TObject);
    procedure acInsertarImagenExecute(Sender: TObject);
    procedure acEliminarImagenExecute(Sender: TObject);
    procedure acEliminarImagenUpdate(Sender: TObject);
    procedure cbxSelectorChange(Sender: TObject);
  private
    { Private declarations }
    function InsertarImagen(AImage: TImage): Integer;
    procedure EliminarImagen(AIDBanner: Integer);
  public
    { Public declarations }
  end;

var
  frmBanners: TfrmBanners;

implementation

{$R *.fmx}

uses IniFiles;

procedure TfrmBanners.FormCreate(Sender: TObject);
var
  sPath: String;
begin
  with TINIFile.Create(ExtractFilePath(ParamStr(0))+'bbdd.ini') do
  begin
    try
      sPath:= ReadString('Database','PATH_BBDD','');
      if (Trim(sPath) = '') then
      begin
        if OpenDialog1.Execute then
           sPath:= Opendialog1.FileName
        else raise Exception.Create('Es necesario fijar la conexión a bbdd');
        WriteString('Database','PATH_BBDD', sPath);
      end;
      demos_interbase.Params.Values['Database'] := sPath;
    finally
      Free;
    end;
  end;
  banners.Open;
end;

function TfrmBanners.InsertarImagen(AImage: TImage): Integer;
var
  FStream: TMemoryStream;
begin
  Result:= -1;
  FStream:= TMemoryStream.Create;
  try
     AImage.Bitmap.SaveToStream(FStream);
     case cbxSelector.ItemIndex of
     0: begin
           spInsertarImagen_Interbase.Params.ParamByName('Banner').LoadFromStream(FStream, ftGraphic);
           spInsertarImagen_Interbase.ExecProc;
           Result:= spInsertarImagen_Interbase.Params.ParamByName('IDBanner').AsInteger;
        end;
     1: begin
           //
        end;
     end;
  finally
    FreeAndNil(FStream);
  end;
end;

procedure TfrmBanners.acEliminarImagenExecute(Sender: TObject);
begin
  if Messagedlg('¿Deseas eliminar la imagen?', System.UITypes.TMsgDlgType.mtWarning ,
                FMX.Dialogs.mbYesNo, 0) = mrYes  then
  begin
    EliminarImagen(banners.FieldByName('IDBanner').AsInteger);
    Banners.Refresh;
  end;
end;

procedure TfrmBanners.acEliminarImagenUpdate(Sender: TObject);
begin
  acEliminarImagen.Enabled:= not banners.IsEmpty;
end;

procedure TfrmBanners.acInsertarImagenExecute(Sender: TObject);
var i: Integer;
begin
  if OpenDialog1.Execute then
  begin
     Image1.Bitmap.LoadFromFile(OpenDialog1.FileName);
     i:= InsertarImagen(Image1);
     Banners.Refresh;
     if i>0 then banners.Locate('IDBanner', i, []);
  end;
end;

procedure TfrmBanners.cbxSelectorChange(Sender: TObject);
begin
  banners.Close;
  demos_interbase.Connected:= False;
  case cbxSelector.ItemIndex of
     0: begin
          banners.Connection:= demos_interbase;
        end;
     1: begin
          raise Exception.Create('Prueba con otras bases de datos aquí.');
        end;
  end;
  banners.Open;
end;

procedure TfrmBanners.EliminarImagen(AIDBanner: Integer);
begin
  case cbxSelector.ItemIndex of
     0: begin
         spEliminarImagen_Interbase.Params.ParamByName('IDBanner').Value:= AIDBanner;
         spEliminarImagen_Interbase.ExecProc;
        end;
     1: begin
         //
        end;
  end;
end;

end.

Durante la creación del formulario, nos aseguramos que está correctamente asignada la ruta a la base de datos (nuestro fichero gdb de Interbase), dado que desde aquí y en este momento no puedo saber en que lugar vais a ubicar el código. Por ello, se lanza el cuadro de dialogo inicial que va a leer de un fichero ini, inicialmente vacío, en el que se guardará la ruta que habéis elegido.

Si todo esto es correcto, se procede a abrir la tabla de imágenes y nuestro botón Eliminar queda activado en función de que existan registros en la tabla. Ambos botones, Insertar y Eliminar se vinculan a su respectiva acción como se puede apreciar.

En la parte privada de la clase hemos declarado dos funciones para cada método, invocados desde el evento que representa la ejecución de la acción. Así, la ejecución de eliminar se apoyara en el valor del indice entero IDBanner del registro activo, para saber cual imagen vamos a borrar (previo al adecuado mensaje de dialogo que asegura que realmente nuestro usuario desea borrar).

    EliminarImagen(banners.FieldByName('IDBanner').AsInteger);

Y la imagen activa, igualmente, servirá como parámetro en la función que ejecuta la inserción.

AImage, en el interior de la función InsertarImagen( ), representa el parámetro que ha recibido la función tras la elección previa de nuestro usuario del fichero gráfico que desea insertar.

...  
  FStream:= TMemoryStream.Create;
  try
     AImage.Bitmap.SaveToStream(FStream);
     case cbxSelector.ItemIndex of
     0: begin
           spInsertarImagen_Interbase.Params.ParamByName('Banner').LoadFromStream(FStream, ftGraphic);
           spInsertarImagen_Interbase.ExecProc;
           Result:= spInsertarImagen_Interbase.Params.ParamByName('IDBanner').AsInteger;
        end;
...

El resultado de la ejecución del procedimiento en la base de datos, será el índice de la imagen tras la inserción y nos apoyaremos en este retorno para localizar dicho registro tras refrescar el dataset, para que sean visibles los cambios.

Si todo va bien, si el servicio se ejecuta y está escuchando adecuadamente en el puerto, si habéis seleccionado correctamente la base de datos, etc. la aplicación mostrará unas cuantas imágenes  en una tabla que podéis recorrer mediante el navegador. Ahora, una vez hecho todo esto, podemos pasar a crear el servidor y el cliente de datasnap, que era realmente nuestro objetivo cuando empezamos a escribir la entrada.

Sencillo, ¿no?

Lo vemos en la segunda parte.

Un comentario sobre “¡Dar una buena imagen! Parte I

Agrega el tuyo

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Blog de WordPress.com.

Subir ↑

Recetas y consejos nutricionales

Indicadas para personas con diabetes, recomendadas para todos.

¡Buen camino!

ANÉCDOTAS Y REFLEXIONES SOBRE UN VIAJE A SANTIAGO…

http://lfgonzalez.visiblogs.com/

Algunas reflexiones y comentarios sobre Delphi

It's All About Code!

A blog about Delphi and related technologies

The Podcast at Delphi.org

The Podcast about the Delphi programming language, tools, news and community.

Blog de Carlos G

Algunas reflexiones y comentarios sobre Delphi

The Road to Delphi

Delphi - Free Pascal - Oxygene

La web de Seoane

Algunas reflexiones y comentarios sobre Delphi

El blog de cadetill

Cosas de programación....... y de la vida

Delphi-losophy

A Lover of Delphi Wisdom

Delphi en Movimiento

Algunas reflexiones y comentarios sobre Delphi

marcocantu.blog

Algunas reflexiones y comentarios sobre Delphi

/*Prog*/ Delphi-Neftalí /*finProg*/

Blog sobre programación de Neftalí -Germán Estévez-

Press F9

Algunas reflexiones y comentarios sobre Delphi

El blog de jachguate

Un blog sobre tecnología y la vida en general

A %d blogueros les gusta esto: