Showing posts with label Delphi 2010. Show all posts
Showing posts with label Delphi 2010. Show all posts

Thursday, 19 August 2010

TBookMark problem with Delphi 2010 and TList

Going on with the big rewrite of code from Delphi 2007 to 2010 (most of it an adaptation more than a rewrite), but we've found some hidden problems as a result of changes appeared in the new version. One of the flaws was shown while I was working with TBookMark class, trying to position a dataset in a given bookmark. With the help of the Embarcadero Forum, we achieved a solution by using "generics" (I'm preparing a post with generics in Delphi 2010) because the TBookMark is now of type TBytes.
Then, the solution would be something similar to this:
uses
    Generics.Collections;

var
    bookmarkList: TList<TBookmark>; //The same as TList<SysUtils.TBytes>

//Adding the bookmark
bookmarkList.Add(cds.GetBookmark);

//We don't need the TBookMark cast
procedure TForm1.GotoBkMarksClick(Sender: TObject);
var
    Cnt: Integer;
begin
    for Cnt := 0 to Pred(BookmarkList.Count) do
    begin
        if DataSet.BookmarkValid(BookmarkList[Cnt]) then
        begin
            DataSet.GotoBookmark(BookmarkList[Cnt]);
            ShowMessage(DataSet.FieldByName('Id').AsString);
       end;
    end;
end;
You need to take into account that every project is different and the use of the TBookMark can differ from one project to another.

Wednesday, 18 August 2010

TIniFile looses unicode characters

We've recently started converting code from Delphi 2007 to Delphi 2010 and we noticed that the TInifile looses unicode characters when we try to save an unicode string into the file. The file is UTF-8 encoding and when we try to save characters like 'ó', 'ç', 'á', etc., they don't appear or some unrecognised characters are shown into the ini file instead of the normal ones.
If the file is ANSI encoded and we try yo use the Tinifile, it will work fine with the unicode characters but not if the ini file is converted to UTF-8.
To solve this, we can use the TMemIniFile class. TMemInifile has an overload for the constructor that allows you to pass the encoding used for the file. The code example to solve this is the following:
procedure TForm1.Button1Click(Sender: TObject);
var
    inifile : TMemIniFile;
    desc : string;
    temp : WideString;
begin
   inifile := TMemIniFile.Create('C:\fileIni.ini', TEncoding.UTF8);
   temp := 'óáç';
   desc := string(temp);
   inifile.WriteString('Section', desc, 'S');
   inifile.UpdateFile;
   inifile.Free;
end;

Tuesday, 17 August 2010

Reading the Exif and IPTC information from a JPEG image with Delphi

Most of you know about my passion with photography and these days I've been working on a new project setting up my own photographic web gallery. I know that I've been more focused on photography than programming, but it's one of my hobbies and after doing some courses I can say that I really enjoy it!. Anyway, going on with my passion, I started the gallery with Flash and XML, and the big problem here was the modification of all the pictures. This tedious task let me build a set of applications to increase speed and productivity while I was uploading the pictures into the web. I started creating Thundax Batch Watermark, a very powerful tool that let you add a watermark to your pictures. Then, few days ago, I released the Thundax Image Resizer to built the resized image with its thumbnail, and finally the Thundax Exif Information to obtain all the Exif (Exchangeable image file format) information contained into the picture with all the information about the camera, exposure time, focal length, etc.
This information is contained into the Resume section of the file:

And I've been struggling in how to get that with Delphi. After a few hours of research, I found the CCR Exif Library for Delphi, a very simple and powerful library for reading Exif and IPTC information. Now with my application we can get the most important information and use it to publish your work:
The library is very easy to use and with a very little lines of code we can extract all the information we want and present it in the desired format.
The code looks like this:
procedure TForm1.ListView1Click(Sender: TObject);
var
  ExifData: TExifData;
  JPEGFile : string;
begin
    if ListView1.ItemIndex = -1 then
        Exit;
    ExifData := nil;
    imgThumbnail.Picture.Assign(nil);
    Memo1.Clear;
    JPEGFile := ListView1.Items[ListView1.ItemIndex].Caption;
    try
        ExifData := TExifData.Create;
        ExifData.EnsureEnumsInRange := False; 
        ExifData.LoadFromJPEG(JPEGFile);
        if ExifData.Empty then
            Memo1.lines.Add('No Exif metadata found')
        else
            LoadStandardValues(ExifData);

      if imgThumbnail.Picture.Graphic <> nil then
      begin
        grpThumbnail.Width := (grpThumbnail.Width - imgThumbnail.Width) +
          imgThumbnail.Picture.Width;
        grpThumbnail.Visible := True;
      end;
    finally
        ExifData.Free;
    end;
end;

Now we can publish our pictures with all the camera information like this:

Camera model: PENTAX K10D       

Date/time: 03/01/2009 17:55:05

Resolution: 72 x 72 inches

Exposure time: 0,3 seconds

F number: F/4

Focal length: 28,13 mm

ISO speed rating(s): 400


You can find more information about this in the following links:

Wednesday, 11 August 2010

Resizing a JPEG Image with Thundax Image Resizer

These days I've been focused on a new web gallery for my pictures and I've come up with the idea of developing a simple application using Delphi 2010 to help me with the tedious task of resizing all my pictures in different sizes and creating thumbnails as well. I used and modified the code from Andrew Jameson to smoothly resize a JPEG image. With my last application you can resize an image in 3 different sizes in one shoot, defining them into the program. The application is called Thundax Image Resizer and you can download it for free. It can resize JPEG images and it can do it in batch.

Here you can see an image of the program:
Once the picture is selected and we resize de image, we'll get the new resized images into the output directory with the name concatenated with its resolution.
Afterwards, we can check our pictures resized and keeping a high quality:

Here you can get the code of the unit LibResize.pas:

unit LibResize;

interface

uses
    jpeg, windows, Graphics, SysUtils, Classes, StrUtils;

type
    TRGBArray = array [Word] of TRGBTriple;
    pRGBArray = ^TRGBArray;

procedure ResizeImage(path : string; FileName: string; MaxWidth: Integer; quality : integer);

implementation

procedure SmoothResize(Src, Dst: TBitmap);
var
    x, y: Integer;
    xP, yP: Integer;
    xP2, yP2: Integer;
    SrcLine1, SrcLine2: pRGBArray;
    t3: Integer;
    z, z2, iz2: Integer;
    DstLine: pRGBArray;
    DstGap: Integer;
    w1, w2, w3, w4: Integer;
begin
    Src.PixelFormat := pf24Bit;
    Dst.PixelFormat := pf24Bit;

    if (Src.Width = Dst.Width) and (Src.Height = Dst.Height) then
        Dst.Assign(Src)
    else
    begin
        DstLine := Dst.ScanLine[0];
        DstGap := Integer(Dst.ScanLine[1]) - Integer(DstLine);
        xP2 := MulDiv(pred(Src.Width), $10000, Dst.Width);
        yP2 := MulDiv(pred(Src.Height), $10000, Dst.Height);
        yP := 0;

        for y := 0 to pred(Dst.Height) do
        begin
            xP := 0;
            SrcLine1 := Src.ScanLine[yP shr 16];

            if (yP shr 16 < pred(Src.Height)) then
                SrcLine2 := Src.ScanLine[succ(yP shr 16)]
            else
                SrcLine2 := Src.ScanLine[yP shr 16];

            z2 := succ(yP and $FFFF);
            iz2 := succ((not yP) and $FFFF);
            for x := 0 to pred(Dst.Width) do
            begin
                t3 := xP shr 16;
                z := xP and $FFFF;
                w2 := MulDiv(z, iz2, $10000);
                w1 := iz2 - w2;
                w4 := MulDiv(z, z2, $10000);
                w3 := z2 - w4;
                DstLine[x].rgbtRed := (SrcLine1[t3].rgbtRed * w1 + SrcLine1[t3 + 1].rgbtRed * w2 + SrcLine2[t3].rgbtRed * w3 + SrcLine2[t3 + 1]
                        .rgbtRed * w4) shr 16;
                DstLine[x].rgbtGreen := (SrcLine1[t3].rgbtGreen * w1 + SrcLine1[t3 + 1].rgbtGreen * w2 +
                        SrcLine2[t3].rgbtGreen * w3 + SrcLine2[t3 + 1].rgbtGreen * w4) shr 16;
                DstLine[x].rgbtBlue := (SrcLine1[t3].rgbtBlue * w1 + SrcLine1[t3 + 1].rgbtBlue * w2 + SrcLine2[t3].rgbtBlue * w3 + SrcLine2[t3 + 1]
                        .rgbtBlue * w4) shr 16;
                Inc(xP, xP2);
            end;
            Inc(yP, yP2);
            DstLine := pRGBArray(Integer(DstLine) + DstGap);
        end;
    end;
end;

function LoadJPEGPictureFile(Bitmap: TBitmap; FilePath, FileName: string): Boolean;
var
    JPEGImage: TJPEGImage;
begin
    if (FileName = '') then
        Result := False
    else
    begin
        try
            JPEGImage := TJPEGImage.Create;
            try
                JPEGImage.LoadFromFile(FilePath + FileName);
                Bitmap.Assign(JPEGImage);
                Result := true;
            finally
                JPEGImage.Free;
            end;
        except
            Result := False;
        end;
    end;
end;

function SaveJPEGPictureFile(Bitmap: TBitmap; FilePath, FileName: string; Quality: Integer): Boolean;
var
    size: string;
    extension: string;
    newName: string;
begin
    Result := true;
    try
        if ForceDirectories(FilePath) then
        begin
            with TJPEGImage.Create do
            begin
                try
                    Assign(Bitmap);
                    CompressionQuality := Quality;
                    size := '_' + IntToStr(Bitmap.Width) + 'x' + IntToStr(Bitmap.Height);
                    extension := ExtractFileExt(FileName);
                    newName := AnsiLeftStr(FileName, Length(FileName) - Length(extension)) + size + extension;
                    SaveToFile(FilePath + newName);
                finally
                    Free;
                end;
            end;
        end;
    except
        raise ;
        Result := False;
    end;
end;

function JPEGDimensions(FileName: string; var x, y: Word): Boolean;
var
    SegmentPos: Integer;
    SOIcount: Integer;
    b: byte;
begin
    Result := False;
    with TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone) do
    begin
        try
            Position := 0;
            Read(x, 2);
            if (x <> $D8FF) then
                exit;
            SOIcount := 0;
            Position := 0;
            while (Position + 7 < size) do
            begin
                Read(b, 1);
                if (b = $FF) then
                begin
                    Read(b, 1);
                    if (b = $D8) then
                        Inc(SOIcount);
                    if (b = $DA) then
                        break;
                end;
            end;
            if (b <> $DA) then
                exit;
            SegmentPos := -1;
            Position := 0;
            while (Position + 7 < size) do
            begin
                Read(b, 1);
                if (b = $FF) then
                begin
                    Read(b, 1);
                    if (b in [$C0, $C1, $C2]) then
                    begin
                        SegmentPos := Position;
                        dec(SOIcount);
                        if (SOIcount = 0) then
                            break;
                    end;
                end;
            end;
            if (SegmentPos = -1) then
                exit;
            if (Position + 7 > size) then
                exit;
            Position := SegmentPos + 3;
            Read(y, 2);
            Read(x, 2);
            x := Swap(x);
            y := Swap(y);
            Result := true;
        finally
            Free;
        end;
    end;
end;

procedure ResizeImage(path : string; FileName: string; MaxWidth: Integer; quality : integer);
var
    OldBitmap: TBitmap;
    NewBitmap: TBitmap;
begin
    OldBitmap := TBitmap.Create;
    try
        if LoadJPEGPictureFile(OldBitmap, ExtractFilePath(FileName), ExtractFileName(FileName)) then
        begin
            if (OldBitmap.Width > MaxWidth) then
            begin
                NewBitmap := TBitmap.Create;
                try
                    NewBitmap.Width := MaxWidth;
                    NewBitmap.Height := MulDiv(MaxWidth, OldBitmap.Height, OldBitmap.Width);
                    SmoothResize(OldBitmap, NewBitmap);
                    SaveJPEGPictureFile(NewBitmap, path, ExtractFileName(FileName), quality)
                finally
                    NewBitmap.Free;
                end;
            end;
        end;
    finally
        OldBitmap.Free;
    end;
end;

end.

Wednesday, 2 June 2010

Sending objects between applications with Delphi 2007 and Delphi 2010

Nowadays we know how difficult it is to send objects between applications in an easy way. That's what I've been working the last days with the great idea of Omar who was designing the case test, trying to come up with a proper way of sending objects between applications in Delphi 2007 and Delphi 2010. The biggest problem here is data serialization, and that's because I've tried both Delphi's version using the JSON library to serialize data and the Clipboard object as an intermediate bridge component for applications.
I've tested different solutions like using Windows messages, SuperObject framework, Memory map files, etc., instead of using the Clipboard object, but none of them were suitable for me. Even the Clipboard isn't a good option but in the same application I can play a lot with it. The problem that we need to face up here is that if we want to send objects between applications we can't send the pointer of the object and wait for the second application to recover the information from it. What we need to do is serialize the object to a well-know format (String, Record, etc) and de-serialize the information to recover our object.
Why Have I used both Delphis? Because in Delphi 2010 we actually have the JSON library with us, and we don't have to install anything to marshall/unmarshall our classes.


Approximation with Delphi 2007:
For this example, we need to download the JSON Library (uLKJSON) and create a new class inherited from TlkJSONObject:

TmyObject = class(TlkJSONobject)
    private
        FDescription: AnsiString;
        FId: integer;
    public
        procedure SetId(const Value: integer);
        procedure SetDescription(const Value: AnsiString);
        function GetId: Integer;
        function GetDescription: string;
        property Description: AnsiString read GetDescription write SetDescription;
        property Id: integer read GetId write SetId;
        procedure FillJson();
        procedure Assigned(o : TlkJSONobject);
    end;
    
{ TmyObject }

procedure TmyObject.Assigned(o: TlkJSONobject);
begin
    Self.FDescription := (o.Field['Description'] as TlkJSONstring).Value;
    Self.FId := (o.Field['Id'] as TlkJSONnumber).value;
end;

procedure TmyObject.FillJson;
begin
    Self.Add('Description', TlkJSONstring.Generate(FDescription));
    Self.Add('Id', TlkJSONnumber.Generate(FId));
end;

function TmyObject.GetId: Integer;
begin
    result := FId;
end;

function TmyObject.GetDescription: AnsiString;
begin
    Result := FDescription;
end;

procedure TmyObject.SetId(const Value: integer);
begin
    FId := Value;
end;

procedure TmyObject.SetDescription(const Value: AnsiString);
begin
    FDescription := Value;
end;

Take a look at the assignment section where we need to fill in all the data using the JSON casting classes (TlkJSONString, TlkJSONNumber, etc).
Then, when we create our new class, we need to do:

uses
    uLKJson;

var
  js: TmyObject;

  js := TmyObject.Create;
  js.Description := 'desc';
  js.Id := 1111;
  js.FillJson;
  s := TlkJSON.GenerateText(js);

Then, if we want to send the JSON Object from one application to another, we can use the ClipBoard object or the windows messaging. In this solution I'll show you the second one as in the next example you'll see the managing with the clipboard.

Sender:

procedure TForm1.SendString(s : string);
var
    copyDataStruct: TCopyDataStruct;
begin
    copyDataStruct.dwData := 0; //use it to identify the message contents
    copyDataStruct.cbData := 1 + Length(s);
    copyDataStruct.lpData := PChar(s);
    SendData(copyDataStruct);
end;

procedure TForm1.SendData(const copyDataStruct: TCopyDataStruct);
var
    receiverHandle: THandle;
    res: integer;
begin
    receiverHandle := FindWindow(PChar('TForm2'), PChar('Form2'));
    if receiverHandle = 0 then
    begin
        ShowMessage('CopyData Receiver NOT found!');
        Exit;
    end;
    res := SendMessage(receiverHandle, WM_COPYDATA, Integer(Handle), Integer(@copyDataStruct));
end;

Receiver:

TForm2 = class(TForm)
    Memo1: TMemo;
    private
        procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
    public
        { Public declarations }
end;

procedure TForm2.WMCopyData(var Msg: TWMCopyData);
var
    test: String;
    js : TlkJSONobject;
    tt : TmyObject;
begin
    test := PChar(Msg.CopyDataStruct.lpData);
    js := TlkJSON.ParseText(test) as TlkJSONobject;
    tt := TmyObject.Create();
    tt.Assigned(js);

    Memo1.lines.Add(tt.Description);
    Memo1.lines.Add(IntToStr(tt.Id));
    //Send something back
    msg.Result := -1;
end;

Approximation with Delphi 2010:
In this example, you'll see a little example using the clipboard spy by Zarko Gajic. With his solution, we can start a form in promiscuous mode listening all the messages that belong to the clipboard. Delphi 2010 incorporates in this version the DBXJSON, and DBXJSONReflect units that work with JSON. Now with simple functions we can create Converters and Reverters of our classes.

First of all, we need to define our class:

unit Customer;

interface

uses
  DBXJSON, DBXJSONReflect, SysUtils, Contnrs;

type
  TMaritalStatus = (msMarried, msEngaged, msEligible);

  TCustomer = class
  private
    FName: string;
    FAge: integer;
    FMaritalStatus: TMaritalStatus;
  public
    property Name: string read FName write FName;
    property Age: integer read FAge write FAge;
    property MaritalStatus: TMaritalStatus read FMaritalStatus write FMaritalStatus;
    function ToString: string; override;
    constructor Create(Name: string; Age: integer; MaritalStatus: TMaritalStatus);
  end;

  TCustomerList = class(TObjectList)
  protected
    function GetItem(Index: integer): TCustomer; overload;
    procedure SetItem(Index: integer; AObject: TCustomer);
  public
    property Items[Index: integer]: TCustomer read GetItem write SetItem; default;
  end;

  TManager = class(TObject)
  private
    FCustomerList: TCustomerList;
    procedure SetCustomerList(const Value: TCustomerList);
  public
    property CustomerList: TCustomerList read FCustomerList write SetCustomerList;
    constructor Create;
    destructor Destroy; override;
    class function CreateAndInitialize: TManager;
  end;

Then we need to define a little record that will attach a pointer of our object. This record will be sent through the clipboard and the other application setted up in promiscuous mode, will remain waiting until it receives a message that has information for it.

TTransfer = record
    Handle: Cardinal;
    JSONValue: TJSONObject;
    manager : TManager;
    s : PUnicodeString; //PAnsiString;
  end;

Converter / Reverter functions: These functions are following the structure that JSON needs to marshall/unmarshall our class, but we need to serialize / de-serialize all the class attributes in order to match our variable definition with JSON variable definition (ex: TObjectList -> TListOfObjects):

function ManagerToJSON(manager: TManager): TJSONObject;
var
  m: TJSONMarshal;
begin
  if Assigned(manager) then
  begin
    m := TJSONMarshal.Create(TJSONConverter.Create);
    m.RegisterConverter(TManager, 'FCustomerList', function(Data: TObject; Field: String): TListOfObjects
var
  customers: TCustomerList;
  i: integer;
begin
  customers := TManager(Data).FCustomerList;
  SetLength(result, customers.Count);
  if customers.Count > 0 then
    for i := 0 to customers.Count - 1 do
      result[i] := customers[i];
 end);
    try
      result := m.Marshal(manager) as TJSONObject;
    finally
      m.Free;
    end;
  end;
end;

function JSONToManager(json: TJSONObject): TManager;
var
  unm: TJSONUnMarshal;
begin
  unm := TJSONUnMarshal.Create;
  unm.RegisterReverter(TManager, 'FCustomerList', procedure(Data: TObject; Field: String; Args: TListOfObjects)var obj: TObject;
customers :
TCustomerList;
Customer :
TCustomer;
i :
integer;
begin
  customers := TManager(Data).FCustomerList;
  customers.Clear;
  for obj in Args do
  begin
    Customer := obj as TCustomer;
    customers.Add(TCustomer.Create(Customer.Name, Customer.Age, Customer.MaritalStatus));
  end;
end);
  try
    result := unm.Unmarshal(json) as TManager;
  finally
    unm.Free;
  end;
end;

Setting up the form in promiscuous mode:

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    procedure WMChangeCBChain(var Msg: TMessage); message WM_CHANGECBCHAIN;
    procedure WMDrawClipboard(var Msg: TMessage); message WM_DRAWCLIPBOARD;
  public
  end;

var
  Form1: TForm1;
  transfer: TTransfer;
  transferPointer: ^TTransfer;
  OurFormat: Integer;
  MemberHandle: THandle;
  NextInChain: THandle;

procedure TForm1.WMDrawClipboard(var Msg: TMessage);
var
  MemberPointer: ^TTransfer;
  MemberInClip: THandle;
  AMember: TTransfer;
  han: Cardinal;
  myJSONCustomer: TManager;
  obj: TJSONObject;
  i: Integer;
  man: TManager;
begin
  if Clipboard.HasFormat(cf_text) then
  begin
    Memo1.Lines.Clear;
    Memo1.PasteFromClipboard;
  end
  else if Clipboard.HasFormat(OurFormat) then
  begin
    if OpenClipboard(Handle) then
    begin
      MemberInClip := GetClipboardData(OurFormat);
      MemberPointer := GlobalLock(MemberInClip);
      han := MemberPointer^.Handle;
      obj := MemberPointer^.JSONValue;
      man := MemberPointer^.Manager;
      if Assigned(man) then
      begin
        i := man.CustomerList.Count;
      end;
      myJSONCustomer := JSONToManager(obj);
      GlobalUnLock(MemberInClip);
      CloseClipboard();
      with Memo1.Lines do
      begin
        Clear;
        Add('Clipboard has TMember data:');
        for i := 0 to myJSONCustomer.CustomerList.Count - 1 do
        begin
          Add(IntToStr(myJSONCustomer.CustomerList.Items[i].Age));
          Add(myJSONCustomer.CustomerList.Items[i].Name);
        end;
      end;
    end;
  end;

  if NextInChain <> 0 then
    SendMessage(NextInChain, WM_DrawClipboard, 0, 0);
end;  

procedure TForm1.FormCreate(Sender: TObject);
begin
  NextInChain := SetClipboardViewer(Self.Handle);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  GlobalFree(MemberHandle);
  ChangeClipboardChain(Handle, NextInChain);
end;

procedure TForm1.WMChangeCBChain(var Msg: TMessage);
var
  Remove, Next: THandle;
begin
  Remove := Msg.WParam;
  Next := Msg.LParam;
  with Msg do
    if NextInChain = Remove then
      NextInChain := Next
    else if NextInChain <> 0 then
      SendMessage(NextInChain, WM_CHANGECBCHAIN, Remove, Next)
end;

Sending data through the clipboard:

procedure TForm1.Button1Click(Sender: TObject);
var
  Manager: TManager;
  obj: TJSONObject;
  s: unicodestring;
  myJSONCustomer: TManager;
  i: Integer;
  StringBytes: TBytes;
  JSON: TJSONObject;
begin
  Manager := TManager.Create();
  Manager.CustomerList.Add(TCustomer.Create('Pedro0', 30, msEligible));
  Manager.CustomerList.Add(TCustomer.Create('Pedro1', 31, msEligible));
  Manager.CustomerList.Add(TCustomer.Create('Pedro2', 32, msEligible));
  Manager.CustomerList.Add(TCustomer.Create('Pedro3', 33, msEligible));
  Manager.CustomerList.Add(TCustomer.Create('Pedro4', 34, msEligible));
  Manager.CustomerList.Add(TCustomer.Create('Pedro5', 35, msEligible));

  obj := ManagerToJSON(Manager);
  s := obj.ToString;
  Memo1.Lines.Add(s);

  OurFormat := RegisterClipboardFormat('CF_TTransfer');
  Memo1.Lines.Add('Format: ' + IntToStr(OurFormat));
  if OpenClipboard(Self.Handle) then
  begin
    EmptyClipboard;
    MemberHandle := GlobalAlloc(GMEM_DDESHARE or GMEM_MOVEABLE, SizeOf(TTransfer));
    transferPointer := GlobalLock(MemberHandle);

    transferPointer.Handle := Handle;
    transferPointer.JSONValue := obj;
    transferPointer.Manager := Manager;
    transferPointer.s := @s;

    StringBytes := TEncoding.ASCII.GetBytes(s);
    JSON := TJSONObject.Create;
    JSON.Parse(StringBytes, 0);
    Memo1.Lines.Add(JSON.ToString);

    Manager := JSONToManager(obj);
    for i := 0 to Manager.CustomerList.Count - 1 do
    begin
      Memo1.Lines.Add(IntToStr(Manager.CustomerList[i].Age));
      Memo1.Lines.Add(Manager.CustomerList[i].Name);
    end;

    GlobalUnLock(MemberHandle);
    SetClipboardData(OurFormat, MemberHandle);
    CloseClipboard();
  end;
  Manager.Free;
end;

If we take a look at the JSON data format we can see the following string:

{"type":"Customer.TManager","id":1,"fields":{"FCustomerList":[{"type":"Customer.TCustomer","id":2,"fields":{"FName":"Pedro0","FAge":30,"FMaritalStatus":"msEligible"}},{"type":"Customer.TCustomer","id":3,"fields":{"FName":"Pedro1","FAge":31,"FMaritalStatus":"msEligible"}},{"type":"Customer.TCustomer","id":4,"fields":{"FName":"Pedro2","FAge":32,"FMaritalStatus":"msEligible"}},{"type":"Customer.TCustomer","id":5,"fields":{"FName":"Pedro3","FAge":33,"FMaritalStatus":"msEligible"}},{"type":"Customer.TCustomer","id":6,"fields":{"FName":"Pedro4","FAge":34,"FMaritalStatus":"msEligible"}},{"type":"Customer.TCustomer","id":7,"fields":{"FName":"Pedro5","FAge":35,"FMaritalStatus":"msEligible"}}]}}

But, our problem doesn't end here. This fantastic solution only works in the same application, then we can send objects between forms by sending a pointer of it using the clipboard. In this promiscuous mode, all the forms are waiting for its data, because we can send different types to the clipboard and then filter it.

As a solution for handling objects between applications, I've used the same technology, but instead of sending a full object, I use JSON for sending a full string and then do the reconstruction of my object from the JSON string sent.

it's as easy as this:

Sender:

obj := ManagerToJSON(Manager); 
s := obj.ToString; 
Clipboard.AsText := string(s);

Receiver:

var 
    StringBytes : TBytes;
    JSON: TJSONObject;
begin
  s := Clipboard.AsText;
  StringBytes := TEncoding.ASCII.GetBytes(s); 
  JSON := TJSONObject.Create; 
  JSON.Parse(StringBytes, 0); 
  Memo1.Lines.Add(JSON.ToString);

If you come up with different ideas or have a better solution, please feel free to add a comment.

Sunday, 27 December 2009

Saturday, 12 September 2009

más sobre la RTTI de Delphi 2010

Hoy, tocando un poco mas la nueva RTTI de delphi, he encontrado algo similar a la inyección de parámetros! Brutal!. Éstos no están disponibles en tiempo de diseño, pero si que los podemos capturar mediante el uso de la API de la RTTI. De ésta manera, nos permite asignar una série de atributos a nuestros métodos, pero no solo eso, también lo podremos hacer también con propiedades, clases, etc. En éste pequeño ejemplo que os pongo, adaptado del ejemplo que ha hecho Malcolm Groves en su gran explicación sobre los atributos de la RTTI, añado una serie de atributos a unos métodos y luego los recupero con la RTTI API.

En el ejemplo, crearé un objeto que hereda de TCustomAttribute:
The base class for all custom attributes. Attribute instances created by the RTTI unit are owned by those members to which they apply.
Ésta clase es la que tenemos que utilizar para manejar nuestros atributos, por lo tanto si miramos la creación de un atributo personalizado:


type
MyCustomAttribute = class(TCustomAttribute)
private
FId: Integer;
FDescription: string;
public
constructor Create(Id: Integer; const Description: string);
property Id: Integer read FId write FId;
property Description: string read FDescription write FDescription;
end;

{ MyCustomAttribute }

constructor MyCustomAttribute.Create(Id: Integer; const Description: string);
begin
FId := Id;
FDescription := Description;
end;

Ahora que ya tenemos nuestra clase atributo creada, aplicamos éstos atributos a nuestros métodos, de alguna manera me recuerda a la Inyección en Java, ya que solo le indicabas las cosas a inyectar y él se encargaba de la creación de las clases. Pues aquí pasa algo parecido, nosotros solo tenemos que indicar los parámetros y él ya hace la creación:...fabuloso!.


type
TMyCustomClass = class(TObject)
public
[MyCustomAttribute(1, 'Attr1')]
procedure MyProcedure1;
[MyCustomAttribute(2, 'Attr2')]
procedure MyProcedure2;
[MyCustomAttribute(3, 'Attr3')]
procedure MyProcedure3;
end;

{ TMyCustomClass }

procedure TMyCustomClass.MyProcedure1;
begin

end;

procedure TMyCustomClass.MyProcedure2;
begin

end;

procedure TMyCustomClass.MyProcedure3;
begin

end;

Para poder acceder a éstos parámetros, lo único que tenemos que hacer, es realizar la llamada a la famosa RTTI API, mediante los métodos que os puse en el post anterior:


procedure TForm1.LoadAttr(Sender: TObject);
var
ContextRtti: TRttiContext;
RttiType: TRttiType;
RttiMethod: TRttiMethod;
CustomAttr: TCustomAttribute;
begin
Memo1.Clear;
ContextRtti := TRttiContext.Create;
try
RttiType := ContextRtti.GetType(TMyCustomClass);
for RttiMethod in RttiType.GetMethods do
for CustomAttr in RttiMethod.GetAttributes do
if CustomAttr is MyCustomAttribute then
Memo1.lines.Add(Format(
'Method = %s; Attribute = %s, Id = %d, Description = %s',
[RttiMethod.Name, CustomAttr.ClassName, MyCustomAttribute
(CustomAttr).Id, MyCustomAttribute(CustomAttr).Description]));
finally
ContextRtti.Free;
end;
end;

Ahora, con la llamada a éste método, podemos obtener los siguiente:

Bueno, solo nos queda indagar un poco más para ver como podemos explotar ésto aún más y más. Pero por lo visto habrá que mirarse los cambios muy y muy bien.

Friday, 11 September 2009

Jugando con la RTTI en Delphi 2010

Ya he empezado a manejar la nueva RTTI que ofrece Delphi 2010 y me he quedado asombrado. Utilizando unos métodos muy simples podemos obtener la información de todas nuestras clases de una manera muy elegante. Increíble!, os recomiendo pasar por el fichero rtti.pas y ver las tripas de la bestia, ya que podemos ver bastante información ahí sobre las nuevas clases implementadas. Mediante un simple programa, puedo obtener la lista de métodos y propiedades de mis clases e incluso hacer la llamada a éstas. La verdad es que con ésta versión estoy notando mucho cambio. Entre las versiones 2007 y 2009 el cambio era menor y la verdad que migrar de un lado para otro era bastante fácil y rápido. Ahora el concepto cambia totalmente y tenemos un editor muy potente.

Aquí os dejo un trozo de código, para mostrar todos los métodos y propiedades que tiene mi clase TBox, ya veréis que la llamada es muy fácil:


uses
TypInfo, Rtti;

procedure List();
var
context: TRttiContext;
method: TRttiMethod;
properties: TRttiProperty;
begin
context := TRttiContext.Create;
Memo1.Clear;
Memo1.Lines.Add('Properties');
for properties in context.GetType(TBox).GetProperties do
Memo1.Lines.Add(properties.ToString);
Memo1.Lines.Add('Methods');
for method in context.GetType(TBox).GetMethods do
Memo1.Lines.Add(method.ToString);
context.Free;
end;


Y el resultado, aún mejor:



  • Enlaces de interés:
Enum Information using RTTI.
RTTI Basics Delphi 2010.

Thursday, 10 September 2009

Migración de proyectos a Delphi 2010


Bueno, ya he empezado a realizar la migración de mis proyectos a Delphi 2010. Es una tarea ardua y difícil pero que de momento estoy muy interesado en hacer gracias a las nuevas novedades que trae éste editor. Estoy realizando la migración de uno de mis proyectos más explicado aquí en el blog y es que estoy muy interesado en la serialización de objetos que éste nuevo editor ofrece gracias al Marshal/UnMarshal mediante JSON. He encontrado varios artículos muy buenos de blogs que sigo donde se explica fabulosamente bien todo éste concepto de serialización utilizando las nuevas librerías DBXJSONReflect y DBXJSON. También he descubierto los nuevos diagramas UML que dispone el Model View, que son mucho más bonitos que los de su antecesor Delphi 2007 y 2009. Cómo podéis ver en las siguientes imagenes, la nueva estructura de la aplicación Thundax Box Manager, ha cambiado un poco para dar paso a un diseño más cómodo y fácil y donde podré hacer todo el tema de la serialización en cuanto resuelva un problema con la clase TPoint, ya que ésta (que es un record) no se puede serializar utilizando JSON, por lo tanto tendré que hacer una clase que derive de TObject.
Aún tengo que mirarme todo el tema de la RTTI que ha cambiado bastante y mirar de resolver algunos problemas sobre mi aplicación.

Aquí os dejo los nuevos diagramas de la aplicación:

Clase TBox:


Clase TLine:


Class Intersector:

Aquí os dejo también el código de la serialización de la clase TVertex utilizando JSON:

uses
  DBXJSONReflect, DBXJSON;

procedure SerializeVertex();
var
  Mar: TJSONMarshal;     // Serializer
  UnMar: TJSONUnMarshal; // UnSerializer
  vertex: Tvertex;
  SerializedVertex: TJSONObject; // Serialized for of object
begin
  Mar := TJSONMarshal.Create(TJSONConverter.Create);
  try
    try
      vertex := Tvertex.Create(10,10);
      SerializedVertex := Mar.Marshal(vertex) as TJSONObject;
    finally
      FreeAndNil(vertex);
    end;
  finally
    Mar.Free;
  end;
  // Output the JSON version of the vertex object
  Memo1.lines.add(SerializedVertex.ToString);
  // UnMarshalling vertex
  UnMar := TJSONUnMarshal.Create;
  try
    vertex := UnMar.UnMarshal(SerializedVertex) as Tvertex;
    try
      Assert(vertex.x = 10);
      Assert(vertex.y = 10);
    finally
      vertex.Free;
    end;
  finally
    UnMar.Free;
  end;
end;


El resultado de la visualización del TMemo, es la siguiente:

En los próximos post empezaré a indagar sobre el tema del RTTI y que cosas nuevas nos dan para poderlas aprovechar al máximo en nuestras aplicaciones.

  • Enlaces de interés:
Database Connectivity.
Custom Marshalling/Unmarshalling in Delphi 2010.