Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #42
See Also: other Dr.Bob Examines columns or Delphi articles

An earlier version of this article originally appeared on the Borland Developer Network (December 2002).
I have added a new section about the Dfm2Pas tool that was included with Update 3.

Memory: from VCL via VCL for .NET to .NET
In this article, I'll show how to migrate a VCL application (the game of Memory) to VCL for .NET, and finally to WinForms on .NET, all using the Delphi for .NET Preview command-line compiler (Update 1 or later).

The application that we'll examine here is the simple game of memory (my kids love to play it). We start with the VCL edition that I built as an exercise in creating dynamic controls, while writing my monthly Under Construction column for issue #48 of The Delphi Magazine (published by iTec). I will first migrate the original VCL edition to .NET using VCL for .NET, showing the few minor changes that I had to made to the source code (most changes were due to the fact that we only have a Delphi for .NET preview command-line compiler, and not a full IDE with design-time environment, yet). The third and final version is also a .NET edition of Memory, written in Delphi for .NET but this time using WinForms instead of VCL for .NET.

VCL and VCL for .NET
In my view, the main purpose of VCL for .NET is to offer a quick-and-easy migration path for VCL applications. So I was not surprised to learn that I only had to make a few modifications to my Delphi VCL application. The steps to migrate a VCL application to use VCL for .NET are as follows:

Note that the second step (the .dfm information) is only required since the current version of the Delphi for .NET preview command-line compiler cannot link the .dfm files that belong to our forms and frames. This will only be a temporary issue, and will obviously be solved when the final version is shipping (or maybe even in an upcoming update of the preview version).

Unit Namespaces
The first thing that I had to do, was make sure that the units in the uses clause are now using their fully qualified "namespace" name. Originally, the uses clause of mainform.pas was as follows:

  uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls;
In order to use namespaces, Windows becomes Borland.Win32.Windows, Messages becomes Borland.Win32.Messages, etc.. Since I wanted to end with an application that can be compiled with both Delphi 7 (VCL) and Delphi for .NET (VCL for .NET), I decided to use IFDEFs to distinguish between WIN32 and CLR. Ignoring the fact that we can now have three platforms: WIN32, CLR, and LINUX, I decided to use a simple {$IFDEF CLR} for "VCL for .NET" and the {$ELSE} part for the good-old Win32 "VCL".
To cut a long story short, the uses clause of my mainform.pas is now as follows:
  uses
    {$IFDEF CLR}Borland.Win32.Windows{$ELSE}Windows{$ENDIF},
    {$IFDEF CLR}Borland.Win32.Messages{$ELSE}Messages{$ENDIF},
    {$IFDEF CLR}Borland.Delphi.SysUtils{$ELSE}SysUtils{$ENDIF},
    {$IFDEF CLR}Borland.Delphi.Classes{$ELSE}Classes{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Graphics{$ELSE}Graphics{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Controls{$ELSE}Controls{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Forms{$ELSE}Forms{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Dialogs{$ELSE}Dialogs{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.StdCtrls{$ELSE}StdCtrls{$ENDIF};
Note that a shorter way to write this could be the following (which unfortunately doesn't compile in the current preview version of the Delphi for .NET command-line compiler):
  uses
    {$IFDEF CLR}Borland.Win32.{$ENDIF}Windows,
    {$IFDEF CLR}Borland.Win32.{$ENDIF}Messages,
    {$IFDEF CLR}Borland.Delphi.{$ENDIF}SysUtils,
    {$IFDEF CLR}Borland.Delphi.{$ENDIF}Classes,
    {$IFDEF CLR}Borland.Vcl.{$ENDIF}Graphics,
    {$IFDEF CLR}Borland.Vcl.{$ENDIF}Controls,
    {$IFDEF CLR}Borland.Vcl.{$ENDIF}Forms,
    {$IFDEF CLR}Borland.Vcl.{$ENDIF}Dialogs,
    {$IFDEF CLR}Borland.Vcl.{$ENDIF}StdCtrls;
The error message that you get is "Error: Identifier expected but end of file found".

.dfm Information
The next step is something that is only necessary at this time, and probably no longer when the final version of Delphi for .NET (with VCL for .NET) ships. I'm talking about the lack of .dfm streaming in the current version of the preview command-line compiler. This means that the information from the .dfm file is not compiled/linked into our Delphi application. For our example, the mainform.dfm file contained the following:

  object Form1: TForm1
    Left = 248
    Top = 137
    BorderStyle = bsDialog
    ClientHeight = 402
    ClientWidth = 602
    Color = clNavy
    Font.Charset = ANSI_CHARSET
    Font.Color = clMaroon
    Font.Height = -32
    Font.Name = 'Comic Sans MS'
    Font.Style = [fsBold]
    OldCreateOrder = False
    OnCreate = FormCreate
    PixelsPerInch = 96
    TextHeight = 45
  end
There are only two properties that I won't be able to use in the Delphi for .NET language: OldCreateOrder and TextHeight. But all the others can be taken and placed inside a special routine called InitializeControls, which is called in the constructor and implemented as follows:
{$IFDEF CLR}
  constructor TForm1.Create(AOwner: TComponent);
  begin
    inherited;
    InitializeControls;
    FormCreate(Self) // explicit call...
  end;

  procedure TForm1.InitializeControls;
  begin
    Left := 248;
    Top := 137;
    BorderStyle := bsDialog;
    ClientHeight := 402;
    ClientWidth := 602;
    Color := clNavy;
    Font.Charset := ANSI_CHARSET;
    Font.Color := clMaroon;
    Font.Height := -32;
    Font.Name := 'Comic Sans MS';
    Font.Style := [fsBold];
 // OnCreate := FormCreate
  end;
{$ENDIF}
Note the explicit call to FormCreate that I had to make in the constructor (assigning FormCreate to the OnCreate event handler inside InitializeControls is not enough, since by that time the OnCreate event handler won't be called anymore).
Obviously, I had to make sure that the .dfm file is no longer used if CLR is defined (for now at least), as follows:
  implementation
  {$IFNDEF CLR}
    {$R *.dfm}
  {$ENDIF}
Again, this won't be needed with the final version of Delphi for .NET, but if you want to convert VCL applications to VCL for .NET today, this is a technique that you can use.

Main Project
The third step consisted of working on the main project .dpr file. The CreateForm method of Application is no longer available, but you have to create a form and assign it to the MainForm property instead.

  program Memory;
  uses
    {$IFDEF CLR}Borland.VCL.{$ENDIF}Forms,
    MainForm in 'MainForm.pas' {Form1};

  begin
    Application.Initialize;
  {$IFDEF CLR}
    Form1 := TForm1.Create(nil);
    Application.MainForm := Form1;
  {$ELSE}
    Application.CreateForm(TForm1, Form1);
  {$ENDIF}
    Application.Run;
  end.
Also note the use of the IFDEF in the uses clause. This makes sure I can compile the application using the dcc32 command-line compiler (for Win32) as well as the dccil preview command-line compiler (for .NET). The downside of using an IFDEF in the uses clause of the main project file is that you can no longer open the project in the Delphi IDE itself - you'll get an error that an identifier is expected. And even if you modify the code so the project can be loaded, the IDE won't allow you to compile it (it still thinks the uses clause is invalid).
I hope there will be some smart way to work with namespaces in the future, so I can try to keep my VCL and VCL for .NET applications in a single source project.

Miscellaneous
At this time, I could try to compile the mainform.pas unit, which resulted in a number of errors. One of the errors confused me a bit, namely: "Error: Incompatible types: 'Object' and 'Integer'" (on a source line where I assigned something to the Tag property of a button).
As a result of reading this error, I prematurely concluded that for some reason the Tag property was perhaps no longer available in VCL for .NET (I was wrong, the Tag property has only changed type from integer to TObject). Since my implementation of the Memory game uses the Tag property as integer value of each button, I really needed this property - and I need it to be an integer. As quick fix, I decided to derive my own TTagButton from TButton, adding a Tag field of type integer again:

  type
    TTagButton = class(TButton)
    {$IFDEF CLR}
      Tag: Integer;
    {$ENDIF}
    end;
By making the Tag field only appear if CLR is defined, we can use TTagButton instead of TButton and still have a single source application that compiles with Delphi for Win32 as well as the Delphi for .NET preview command-line compiler.

Since I assumed that the Tag property was also missing from the Form, I added a new Tag field to the Form as well. And with the new constructor and InitializeControls (both only with CLR is defined), the new form definition looks as follows:

  type
    TArrayArrayButton = Array of Array of TTagButton;

    TForm1 = class(TForm)
      procedure FormCreate(Sender: TObject);
    private
      { Private declarations }
      Turns: Integer;
      procedure Shuffle(Button: TArrayArrayButton);
      procedure FirstButtonClick(Sender: TObject);
      procedure SecondButtonClick(Sender: TObject);
    {$IFDEF CLR}
      procedure InitializeControls;
    {$ENDIF}
    public
      { Public declarations }
      Tag: Integer;
      procedure CreateBoard(X,Y: Integer);
    {$IFDEF CLR}
      constructor Create(AOwner: TComponent); override;
    {$ENDIF}
    end;
A final addition to the FormCreate event handler made sure that the caption of the Form displays the version of the Delphi compiler that's being used (Delphi for Windows or Delphi for .NET), so we can see in the caption if we're working with a Win32 or a .NET application.
  procedure TForm1.FormCreate(Sender: TObject);
  begin
    Randomize;
    {$IFDEF CLR}
      Caption := 'Memory compiled with Delphi for .NET';
    {$ELSE}
      Caption := 'Memory compiled with Delphi for Win32';
    {$ENDIF}
    Turns := 0;
    CreateBoard(6,4)
  end;

VCL Conclusion
The result of all this is a Memory.dpr file with mainform.pas and .dfm (only for Win32) that compiles to a 374,784 byte Win32 executable or a 665,088 .NET IL executable. The latter loads a little bit slower than its Win32 counterpart, but works exactly the same. When I close the VCL for .NET application, however, I noticed a small delay before the application is destroyed (garbage collection perhaps?). I'm sure all this will be taken care of before the final version of Delphi for .NET (and VCL for .NET) is made available.
Apart from the main project source code and the fact that units in the uses clause must now be specified with their full namespace, there were actually very little changes that I had to make. The .dfm streaming inside InitializeControls is only a temporary necessity, and the Tag property was there after all (I just had to box it).
Note: Update 3 of the Delphi for .NET preview command-line compiler contains a special utility called Dfm2Pas that will assist you in the process of coding the properties from the .DFM file in the .PAS file (generated the InitializeControls method for you automatically). This is a great help in migrating existing VCL applications to VCL for .NET, so make sure to check it out!

Dfm2Pas
The Delphi for .NET preview command-line compiler Update 3 (that's the full name, for those of you who didn't attend the March 18th meeting at POSK) enhanced support with regards to VCL for .NET by including a utility called Dfm2Pas.
In this new section, I'll examine Dfm2Pas in some more detail, and see how it can be very useful when moving Delphi applications to the .NET world. The Dfm2Pas utility parses a Delphi .pas unit (with or without a .dfm file) or a Delphi .dpr main project file, and modifies these files in a number of ways.

Namespaces
First of all, the uses clause of a regular Delphi application contains references like "SysUtils", "Forms" and so on. In the .NET world, units are no longer just referred by this (short) name, but by a fully qualified namespace. This means that SysUtils becomes Borland.Delphi.SysUtils, and Forms becomes Borland.Vcl.Forms.

No more .DFM files
Another - much bigger - change is caused by the fact that the concept of a .DFM file next to a .PAS file is not available in the Delphi for .NET preview command-line compiler. On Win32, the contents of the DFM file are linked as a binary resource to the Win32 executable, where the information (from the .DFM file) is used when components and forms are loaded (streamed).
The Delphi for .NET preview command-line compiler is unable to use external .DFM files, and instead has to rely on the information (property values etc.) to be hardcoded in the .PAS source file instead (to be honest - I have no idea whether or not this will change between now and the final release of Delphi for .NET, but if you look at Visual C# .NET you see a similar behaviour with everything handled inside the C# source file).
All properties values and settings found in the .DFM file are being coded in a special routine called InitializeControls - we'll see an actual example later in this article.

Conditional Compilation
The use of Dfm2Pas is a one-way process at this time: there is no way to undo the changes (apart from manually editing the source files again), and if you add any more components to the .DFM file or change a few properties using the Object Inspector of Delphi, you have to run Dfm2Pas on the original .PAS/.DFM files again. This is the main reason why you should only use Dfm2Pas on a copy of your project, never on the original files.
Having said that, once your application is "relatively cast in stone", you can use Dfm2Pas to convert it from a VCL into a VCL for .NET application, and then use the resulting (modified copy of your original) project to compile for both Win32 (with Delphi 7) and .NET (with the Delphi for .NET preview command-line compiler), since the resulting project will use compiler directives to ensure conditional compilation based on the target platform. Inside the source code, you'll find {$IFDEF CLR} directives that are used to flag .NET specific namespaces and component initialization code.

Supported VCL Units
If you want to know which VCL units and classes are supported by Dfm2Pas, then the best way is to look at the VCL for .NET units and components that are already supported by the Delphi for .NET preview command-line compiler update 3, which can be found in the Source\VCL directory. To help you visualise these components on the Component Palette: the tabs Standard, Additional, Common Controls, Win32, System, Dialogs, Samples, and Win 3.1 are supported. While this is a lot, the biggest thing that we're still missing is database support: I would have hoped to have dbExpress (or a similar solution) technology available on .NET by now. This also means that we don't have any data-aware controls. And apart from that, all web functionality is also missing, but since .NET has it's own web framework (in ASP.NET) that's not a big issue - besides, Dfm2Pas is obviously meant as a help for GUI applications.

Example
As an example application, start Delphi 7, drop a PageControl on the form, followed by a number of components from the Standard, Additional, and other tabs that I just mentioned in the previous section. Write your event handlers as you are used to do. Or just pick one of your existing applications, as long as you make sure it doesn't use any data access components (which is why I have a hard time currently finding a real-world application that can be used - most of my applications are using some kind of a database or (client) dataset to store data).
For my Dfm2Pas demo application, I've used a PageControl, a TEdit, TButtons, etc. and produced a project with unit .pas and .dfm file. The original .pas file is shown below:

  unit MainForm;
  interface
  uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls, ComCtrls;

  type
    TForm1 = class(TForm)
      PageControl1: TPageControl;
      TabSheet1: TTabSheet;
      TabSheet2: TTabSheet;
      Edit1: TEdit;
      Memo1: TMemo;
      Button1: TButton;
      ListBox1: TListBox;
      procedure Button1Click(Sender: TObject);
    private
      { Private declarations }
    public
      { Public declarations }
    end;

  var
    Form1: TForm1;

  implementation
  {$R *.dfm}

  procedure TForm1.Button1Click(Sender: TObject);
  begin
    ListBox1.Items.Add(Edit1.Text)
  end;

  end.
The modified .pas file - after running Dfm2Pas on it, is shown below. Note the entire uses clause which is using the IFDEF structure to add the namespaces Borland.Win32, Borland.Delphi and Borland.Vcl to the units listed. Any unknown units will not be changed unless you add them to the Files section of the Dfm2Pas.ini file (which already contains the MyFile=MyCompany.MyProduct.MyFile example reference).
  // ************************************************************************ //
  // Dfm2Pas: WARNING!
  // -----------------
  // Part of the code declared in this file was generated from data read from
  // a *.DFM file or a Delphi project source using Dfm2Pas 1.0.
  // For a list of known issues check the README file.
  // Send Feedback, bug reports, or feature requests to:
  // e-mail: fvicaria@borland.com or check our Community website.
  // ************************************************************************ //

  unit MainForm;
  interface
  uses
    {$IFDEF CLR}Borland.Win32.Windows{$ELSE}Windows{$ENDIF},
    {$IFDEF CLR}Borland.Win32.Messages{$ELSE}Messages{$ENDIF},
    {$IFDEF CLR}Borland.Delphi.SysUtils{$ELSE}SysUtils{$ENDIF},
    {$IFDEF CLR}Borland.Delphi.Variants{$ELSE}Variants{$ENDIF},
    {$IFDEF CLR}Borland.Delphi.Classes{$ELSE}Classes{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Graphics{$ELSE}Graphics{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Controls{$ELSE}Controls{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Forms{$ELSE}Forms{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.Dialogs{$ELSE}Dialogs{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.StdCtrls{$ELSE}StdCtrls{$ENDIF},
    {$IFDEF CLR}Borland.Vcl.ComCtrls{$ELSE}ComCtrls{$ENDIF};

  type
    TForm1 = class(TForm)
      PageControl1: TPageControl;
      TabSheet1: TTabSheet;
      TabSheet2: TTabSheet;
      Edit1: TEdit;
      Memo1: TMemo;
      Button1: TButton;
      ListBox1: TListBox;
      procedure Button1Click(Sender: TObject);
    private
      { Private declarations }
  {$IFDEF CLR}
      procedure InitializeControls;
  {$ENDIF}
    public
      { Public declarations }
  {$IFDEF CLR}
      constructor Create(AOwner: TComponent); override;
  {$ENDIF}
    end;

  var
    Form1: TForm1;

  implementation
  {$IFNDEF CLR}
  {$R *.dfm}
  {$ENDIF}

  procedure TForm1.Button1Click(Sender: TObject);
  begin
    ListBox1.Items.Add(Edit1.Text)
  end;

  {$IFDEF CLR}
  constructor TForm1.Create(AOwner: TComponent);
  begin
    inherited;
    InitializeControls;
  end;
  {$ENDIF}

  {$IFDEF CLR}
  procedure TForm1.InitializeControls;
  begin
    // Initalizing all controls...
    PageControl1 := TPageControl.Create(Self);
    TabSheet1 := TTabSheet.Create(Self);
    Edit1 := TEdit.Create(Self);
    Memo1 := TMemo.Create(Self);
    Button1 := TButton.Create(Self);
    ListBox1 := TListBox.Create(Self);
    TabSheet2 := TTabSheet.Create(Self);

    with PageControl1 do
    begin
      Name := 'PageControl1';
      Parent := Self;
      Left := 0;
      Top := 0;
      Width := 688;
      Height := 453;
      ActivePage := TabSheet1;
      Align := alClient;
      TabOrder := 0;
    end;

    with TabSheet1 do
    begin
      Name := 'TabSheet1';
      if TabSheet1.ClassType.InheritsFrom(TControl) then
        TControl(TabSheet1).Parent := self;
      Caption := 'TabSheet1';
      PageControl := PageControl1;
    end;

    with Edit1 do
    begin
      Name := 'Edit1';
      Parent := TabSheet1;
      Left := 56;
      Top := 80;
      Width := 121;
      Height := 21;
      TabOrder := 0;
      Text := 'Edit1';
    end;

    with Memo1 do
    begin
      Name := 'Memo1';
      Parent := TabSheet1;
      Left := 248;
      Top := 104;
      Width := 185;
      Height := 89;
      Lines.Add('Memo1');
      TabOrder := 1;
    end;

    with Button1 do
    begin
      Name := 'Button1';
      Parent := TabSheet1;
      Left := 64;
      Top := 232;
      Width := 75;
      Height := 25;
      Caption := 'Button1';
      TabOrder := 2;
      OnClick := Button1Click;
    end;

    with ListBox1 do
    begin
      Name := 'ListBox1';
      Parent := TabSheet1;
      Left := 504;
      Top := 176;
      Width := 121;
      Height := 97;
      ItemHeight := 13;
      TabOrder := 3;
    end;

    with TabSheet2 do
    begin
      Name := 'TabSheet2';
      if TabSheet2.ClassType.InheritsFrom(TControl) then
        TControl(TabSheet2).Parent := self;
      Caption := 'TabSheet2';
      ImageIndex := 1;
      PageControl := PageControl1;
    end;

    // Form's PMEs'
    Left := 270;
    Top := 107;
    Width := 696;
    Height := 480;
    Caption := 'Form1';
    Color := clBtnFace;
    Font.Charset := DEFAULT_CHARSET;
    Font.Color := clWindowText;
    Font.Height := -11;
    Font.Name := 'MS Sans Serif';
    Font.Style := [];
  end;
  {$ENDIF}

  end.
Note that sometimes the order of properties is important. The Dfm2Pas utility will list (and assign) the properties in alphabetical order. For some components, this may not work correctly (for example the ItemIndex property of a TListBox or TComboBox should be assigned after the Items property). You have to manually change the order of these statements in the generated unit if you encounter problems with this.
A good thing is that the IFDEF CLR (and IFNDEF CLR) compiler directives make sure that the new form will compile with Delphi for .NET as well as Delphi 7 (as long as you keep the .DFM file with it as well). Obviously, any controls that will be add to the .DFM file will not be created in the InitializeControls, nor will any new properties values or event handlers be used, so you should only perform Dfm2Pas on the final version and not continue to work on the converted version of your project (unless you know what you're doing).

Dfm2Pas Summary
The Delphi for .NET preview command-line compiler is maturing with VCL for .NET support now that Update 2 is available - the Dfm2Pas is a great help in migrating existing VCL applications to VCL.NET.

And now it's time to cover native .NET WinForms.

WinForms
When I'm talking about WinForms, I mainly refer to the .NET controls in the System.Windows.Forms namespace. Before the availability of VCL for .NET, I had already investigated the ways to convert the VCL application to .NET using the WinForms classes. The result of these efforts was a single .dpr file (see next listing). This time, I had to convert a lot more code, however:

This sounds like a long list, but while it takes more work to convert a VCL application to WinForms (compared to "migrating" VCL to VCL for .NET), it's still fairly easy to do. It's a continuous loop of compile, fix syntax at the first error, recompile, etc. until the application finally compiles entirely. The hardest part often is not the fact that you know what's not available (like Left and Top), but finding out what to use instead (like Location). This is something that will be much easier once you have a visual development IDE, complete with Form Designer and Object Inspector, since that will directly show you the Location (as well as Size) properties. For now, a reference of WinForms controls, properties and events may be useful while converting VCL applications to WinForms with the current edition of the Delphi for .NET preview command-line compiler.
The final source code of the WinForms edition of my simple game of memory is as follows:
  program Memory42;
  uses
    System.Windows.Forms,
    System.ComponentModel,
    System.Drawing,
    Borland.Delphi.SysUtils; // Needed for Sleep()

  const
    Caption = 'Sharpen your mind: Dr.Bob''s Game of Memory for .NET (%d)';

  const
    MaxX = 6;
    MaxY = 4;

  type
    TagButton = class(Button)
      Tag: Integer; // Not part of Windows.Forms.Button
    end;

    TArrayArrayButton = Array[1..MaxX] of Array[1..MaxY] of TagButton;

    TForm42 = class(Form)
    public
      { Public declarations }
      constructor Create;
    protected
      { Protected declarations }
      procedure ButtonClick(Sender: System.Object; eventAgrs: System.EventArgs);
    private
      { Private declarations }
      Tag: Integer; { previous button }
      Turns: Integer;
      First: Boolean; { first or second button? }
      Buttons: TArrayArrayButton;
    end;


  constructor TForm42.Create;
  const
    W = 600;
    H = 400;
  var
    i,j,Tag: Integer;
    X1,X2,Y1,Y2: Integer;
  begin
    inherited Create;
    Randomize;
    Turns := 0;
    Text := Format(Caption,[Turns]);
    First := True;
    Size := Size.Create(W + 12, H + 30);
    for i:=1 to MaxX do
    begin
      for j:=1 to MaxY do
      begin
        Buttons[i,j] := TagButton.Create;
        Buttons[i,j].Location := Point.Create(6 + (W div MaxX) * Pred(i),
                                              6 + (H div MaxY) * Pred(j));
        Buttons[i,j].Size := Size.Create((W div MaxX) - 8,(H div MaxY) - 8);
        Buttons[i,j].Tag := 1 + (Pred(j) * MaxX + Pred(i)) div 2;
        Buttons[i,j].Text := '?';
        Buttons[i,j].Font := Font.Create('Comic Sans MS', 24);
        Buttons[i,j].add_Click(ButtonClick);
        Controls.Add(Buttons[i,j])
      end
    end;
    for i:=1 to 42 do // shuffle
    begin
      X1 := 1+Random(MaxX);
      X2 := 1+Random(MaxX);
      Y1 := 1+Random(MaxY);
      Y2 := 1+Random(MaxY);
      Tag := Buttons[X1,Y1].Tag;
      Buttons[X1,Y1].Tag := Buttons[X2,Y2].Tag;
      Buttons[X2,Y2].Tag := Tag
    end
  end;

  procedure TForm42.ButtonClick(Sender: System.Object; eventAgrs: System.EventArgs);
  var
    i,j: Integer;
  begin
    if First then
    begin
      First := False;
      Inc(Turns);
      Text := Format(Caption,[Turns]);
      Tag := (Sender as TagButton).Tag;
      (Sender as TagButton).Text := IntToStr(Tag)
    end
    else // second button
    begin
      First := True;
      (Sender as TagButton).Text := IntToStr((Sender as TagButton).Tag);
      Update;
      if (Sender as TagButton).Tag = Tag then // the same
      begin
        for i:=1 to MaxX do
          for j:=1 to MaxY do
            if Buttons[i,j].Tag = Tag then
            begin
              Buttons[i,j].Enabled := False;
              BUttons[i,j].Text := Format('[%d]',[Tag])
            end
      end
      else // not the same; hide again
      begin
        Sleep(1000);
        for i:=1 to MaxX do
          for j:=1 to MaxY do
            if Buttons[i,j].Enabled then Buttons[i,j].Text := '?'
      end
    end
  end;

  begin
    Application.Run(TForm42.Create);
  end.
This results in a native .NET IL executable of only 16,896 bytes. Much smaller than the VCL for .NET IL executable, because it uses the WinForms assembly (and doesn't have to link in any of the VCL for .NET units). But it took me more time to produce this application from the original VCL project.

Conclusion
In this article, I've shown how to migrate a VCL application (the game of Memory) to VCL for .NET, as well as to WinForms on .NET, all using the Delphi for .NET Preview command-line compiler. The steps to migrate a VCL application to VCL for .NET are less time consuming than migrating from VCL to WinForms. The pure WinForms application is smaller, however, and runs a bit faster.
In my personal view, VCL for .NET is a good way to quickly migrate VCL applications to .NET, but for new (native) .NET development, I probably won't be using VCL for .NET. I guess time will tell - so I can't wait until the next update of the Delphi for .NET command-line compiler is made available (or the final version of Delphi for .NET).


This webpage © 2003-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.