Memory: from VCL via VCL for .NET to .NET

By: Bob Swart

Abstract: 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).

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).

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.
You can download the complete source code from the Delphi for .NET Samples page, with the complete implementation of the VCL, VCL for .NET, and WinForms editions of Memory.

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 download 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).

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:

  • No more TButton or TForm, just "Button" and "Form"
  • No more Tag property (this time for real)
  • No Left and Top to position a control, but Location
  • No Height and Width properties, but Size instead
  • No Caption of the Form, but Text instead
  • No Parent or Owner of Controls, but Controls.Add
  • Adding OnClick event handler using add_Click method
  • No ComponentCount or Components properties of the Form
  • A different way to call Application.Run
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 (Update 1). 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).

You can download the complete source code from the Delphi for .NET Samples page, with the complete implementation of the VCL, VCL for .NET, and WinForms editions of Memory.
Feel free to send me feedback, comments or any questions about this article. You can either e-mail me directly, or use the borland.public.delphi.netpreview.* newsgroups.


Bob Swart (aka. "Dr.Bob" - http://www.drbob42.com) is author, trainer, consultant and webmaster for his own company Bob Swart Training & Consultancy (eBob42) in Helmond, The Netherlands. Bob is a technical author for The Delphi Magazine, Hardcore Delphi, C++Builder Developer's Journal, Der Entwickler, SDGN Magazine, UK-BUG Developer's Magazine, has written for the websites of DevX, TechRepublic/CNET, the IBM DB2 and Borland portal, and has spoken at (Borland) conferences all over the world since 1994. Bob is co-author of The Revolutionary Guide to Delphi 2, Delphi 4 Unleashed, C++Builder 4 Unleashed, C++Builder 5 Developer's Guide, Kylix Developer's Guide, and Delphi 6 Developer's Guide.
Bob is married to Yvonne and they have two internet-aware children: Erik Mark Pascal (8.5 years) and Natasha Louise Delphine (6 years).


Server Response from: ETNASC03