Eventos V9.6.10: Cambio en registros.

«Oh maestro, bien hallado seas. Largo fue tu cautiverio y grande nuestro desasosiego. Pero guardamos la esperanza  con tus enseñanzas. ¿Fue el oscuro señor magnánimo?» grito alborozado el sorprendido pupilo.
«No, pequeño saltamontes. Un señor tan vil jamás será magnánimo. Solo la cobardía y la codicia guían su mano. Tan oscuros, como su alma, sus propósitos son«

En este antiguo artículo se describían las estructuras de datos que se recibían en los eventos que se generan en la aplicación. A partir de la versión 9.6.10 de a3ERP se ha cambiado el tipo de los valores que se reciben con las estructuras registro; y por lo tanto con la estructura conjunto de datos, ya que no es más que una lista de registros.

Ahora, los valores de los campos (que no los nombres) dejan de ser una cadena para pasar a ser Variant (A grosso modo se trata de una estructura que puede albergar valores de distinto tipo cada vez). Con esto se consigue enviar y recibir los valores tal cual son, sin necesidad de convertirlos a o desde cadena (con los problemas que ello puede acarrear). Y más aún, valores que antes no se podían codificar (serializar) como  cadenas se pueden transportar desde y hacia (en los eventos que lo permitan) la aplicación a través de las respuestas de los eventos. Principalmente hablamos de los campos binarios (blobs, image).

Quedarán afectados todos los eventos que reciban un registro o conjunto de datos.

Recordemos como eran las estructuras:

Tipo Contenido
Registro

Se trata de una matriz multidimensional en la que el primer elemento, que existe siempre, indica el número entero de elementos (campos) que componen la matriz. Estos datos son, a su vez, matrices unidimensionales de dos elementos, en las que primero tenemos el nombre del campo (cadena) y en el segundo el valor de dicho campo (variant).

Matriz:

Array[0] : Número de Campos (desde índice 1 a n).

Array[1]: Campo 1.

Array[n]: Campo N-ésimo.

Campo:

Campo[0]: (string) Nombre del campo.

Campo[1]: (string) Valor del campo.

Para acceder al nombre del quinto campo del registro  tendremos que hacer (Delphi):

MiValor := MiConjuntoDeDatos[5][0]

donde:

5 = Quinto campo.

0=Nombre del campo.

Conjunto de datos

Es una matriz  multidimensional en la que el primer elemento indica el número entero de «Registros» que lo compone. A partir de éste, cada elemento es un Registro.

Matriz:

Array[0]: Número de registros (desde índice 1 a n).

Array[1]: Registro 1.

Array[n]:  Registro n-ésimo.

Así, si queremos acceder al valor del tercer campo del segundo registro de un conjunto de datos tendremos que hacer (Delphi):

MiValor := MiConjuntoDeDatos[2][3][1]

donde:

2 = Segundo registro.

3 = Tercer campo.

1=Valor

Desde Delphi, la conversión a un tipo nativo es automática (en el caso general) y para los casos problemáticos basta con usar las rutinas de la unidad «Variants» (System.Variants en versiones XE).

Los campos binarios se recibirán como listas de bytes. Veamos un ejemplo (Delphi XE7) de uso para recibir y/o enviar una imagen (Se presupone que en el documento que se está guardando hay un campo llamado TEST_IMAGE):

function IsArrayOfByte(const [Ref] AVariantArrayOfByte: Variant): Boolean;
begin
  Result := VarType(AVariantArrayOfByte) = $2011 //array ($2), of byte ($011 = varByte)
end;

function IsEmptyArray(const [Ref] AVariantArrayOfByte: Variant): Boolean;
begin
  Result := VarIsEmpty(AVariantArrayOfByte) or
            VarIsClear(AVariantArrayOfByte) or
           (VarArrayHighBound(AVariantArrayOfByte, 1) = 0);
end;

function BuildStream(const [Ref] AVariantArrayOfByte: Variant): TStream;
var
  ImageBuffer: array of byte;
  ImageSize: Integer;
begin
  ImageSize := VarArrayHighBound(AVariantArrayOfByte, 1);
  if ImageSize = 0 then begin
    Result := nil;
    Exit;
  end;

  ImageBuffer := AVariantArrayOfByte;
  Result := TMemoryStream.Create;
  if ImageSize <> Result.WriteData(ImageBuffer, ImageSize) then begin
    Result.Free;
    raise Exception.Create('Ups i dit it again');
  end;
end;

function BuildGraphic(AStream: TStream): TGraphic; overload;
begin
  // build según extensión... Ejercicio para el lector
  Result := TJPEGImage.Create;
  Result.LoadFromStream(AStream);
end;

function BuildGraphic(const [Ref] AVariantArrayOfByte: Variant): TGraphic; overload;
var
  ImageStream: TStream;
begin
  ImageStream := BuildStream(AVariantArrayOfByte);
  try
    ImageStream.Position := 0;
    Result := BuildGraphic(ImageStream);
  finally
    ImageStream.Free;
  end;
end;

procedure ShowImage(AGraphic: TGraphic);
var
  Form: TForm;
begin
  Form := TForm.Create(nil);
  try
    with TImage.Create(Form) do begin
      AutoSize := True;
      Parent := Form;
      Align := TAlign.alClient;
      Picture.Graphic := AGraphic;
      Form.ClientWidth := Width;
      Form.ClientHeight := Height;
    end;
    Form.ShowModal;
  finally
    Form.Free;
  end;
end;

function LoadImage(const AFilename: TFileName): Variant; overload;
var
  Stream: TStream;
  Buffer: PByte;
begin
  Stream := TFileStream.Create(AFilename, fmOpenRead);
  try
    Result := VarArrayCreate([1, Stream.Size], varByte);

    Buffer := VarArrayLock(Result);
    Stream.Position := 0;
    Stream.Read(Buffer^, Stream.Size);
    VarArrayUnlock(Result);
  finally
    Stream.Free;
  end;
end;


function GetNewImageFilename: TFilename;
begin
  with TOpenPictureDialog.Create(nil) do
  try
    if not Execute then
      Result := ''
    else
      Result := Filename;
  finally
    Free;
  end;
end;

function ANTESDEGUARDARDOCUMENTOV2(Documento: string; IdDoc: Double; var Cabecera: Variant; var Lineas: Variant; Estado:Integer): boolean; stdcall;
var
  Dataset: TDatasetObject;
  ImageValue: Variant;
  Graphic: TGraphic;

  NewImageFilename: TFilename;
begin
  Result := True;

  Dataset := TDatasetObject.Create(Cabecera); 
  try
    // Leer imagen de conjunto de datos recibido
    ImageValue := Dataset.Rows[0].FieldByName('TEST_IMAGE').Value;
    if not IsArrayOfByte(ImageValue) or IsEmptyArray(ImageValue) then Exit;

    Graphic := BuildGraphic(ImageValue);
    try
      ShowImage(Graphic);
    finally
      Graphic.Free;
    end;

    NewImageFilename := GetNewImageFilename;
    if NewImageFilename = '' then Exit;

    // enviar una nueva imagen de vuelta a a3ERP
    ImageValue := LoadImage(NewImageFilename);
    Dataset.Rows[0].FieldByName('TEST_IMAGE').Value := ImageValue;
    Dataset.ApplyTo(Cabecera);
  finally
    Dataset.Free;
  end;
end;

En el mundo .NET, al recibirse las estructuras como matrices de objetos, basta con convertir (cast) al tipo esperado.

(nota: en ambos casos es recomendable revisar los tipos de los campos usando el diccionario de la aplicación).

    public bool AntesDeGuardarDocumentoV2(string docKind, double idDoc, ref object header, ref object lines, int state)
    {
		// recibimos la imagen desde a3ERP
        var dataRow = header.AsDataset().First();
        var field = dataRow.ByFieldName("TEST_IMAGE");
        var imageData = field.Value;

        using (var ms = new MemoryStream(imageData as byte[]))
        {
            using (var image = Image.FromStream(ms))
            {
                var form = new Form();

                var pb = new PictureBox();
                pb.Image = image;
                pb.Parent = form;
                pb.AutoSize = true;
                form.ShowDialog();
            }
        }

        string fileName;
        using (var dialog = new OpenFileDialog())
        {
            if (dialog.ShowDialog() != DialogResult.OK) return true;
            fileName = dialog.FileName;
        }

		// Enviamos una nueva imagen de vuelta a a3ERP
        using (var image = Image.FromFile(fileName))
        {
            using (var ms = new MemoryStream())
            {
                image.Save(ms, image.RawFormat);
                ms.Position = 0;
                var data = ms.ToArray();

                field.Value = data;
            }
        }

        return true;
    }

En los ejemplos, tanto Delphi como C#, se han usado las utilidades que podréis encontrar en este artículo.

«Por cierto, pequeño saltamontes, ¿No recordarás donde dejé mi único bien, mi muy preciada…?» dijo el maestro mientras arqueaba su ceja de manera inquisitorial. «Porque llegó a mis oídos que la denuncia que me tuvo apartado tenía que ver con cierto objeto de bronce que no hallo.» Un ruido sordo y un grito de terror le interrumpió. Prosiguió «La próxima vez que ose defenestrarse el solito, le enseñaré a abrir la ventana».

Acerca de El monstruo de Caerbannog

Temible guardián de la gruta que esconde un temible y obscuro secreto...

Publicado el octubre 6, 2015 en Desarrolladores, Distribuidores, Programación, Versión 9 y etiquetado en , , , . Guarda el enlace permanente. Deja un comentario.

Deja un comentario