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».
Publicado el octubre 6, 2015 en Desarrolladores, Distribuidores, Programación, Versión 9 y etiquetado en c#, código, delphi, dll terceros. Guarda el enlace permanente. Deja un comentario.
Deja un comentario
Comments 0