|
|
|
|
Een groot deel van de bezoekers van mijn website (misschien wel bijna iedereen) gebruikt Borland Delphi.
En het zal vast niemand ontgaan zijn dat Microsoft de afgelopen paar jaar bezig is geweest de oude vertrouwde (maar niet-zo-veilige) Windows API te vervangen door het .NET Framework.
Vooral voor software ontwikkelaars is dat een ontwikkeling die van betekenis is, en grote invloed heeft op onze manier van werken.
Tot en met versie 7 kan Delphi alleen Win32 toepassingen produceren, terwijl Delphi 8 for .NET alleen .NET toepassingen kan opleveren.
Het is Delphi 2005 die zowel Win32 als .NET aankan, wat Delphi 2005 de meest complete Delphi versie maakt tot nu toe (jammer alleen dat er niet ook een Kylix versie in de doos zit).
Waar ik me een beetje over verbaas is dat er wel een 30 dagen trial editie van de hoogste architect versie van Delphi 2005 beschikbaar is, maar niet een gratis uitgeklede editie die gewoon blijft werken.
Delphi 7 Personal was beschikbaar voor iedereen, en was een prima middel om de taal en omgeving te leren kennen.
Delphi 2005 Personal bestaat wel, maar wordt alleen gebruikt (uitgedeeld) bij "bijzondere" gelegenheden, zoals een speciaal "Delphi.NET" blad of seminar dat dan ook nog door een grote partij georiganiseerd moet worden voordat het de aandacht van Borland trekt.
Je kan op zoek naar een speciaal nummer van PC Pro in Engeland, of een speciale uitgave van het Duitse Delphi.NET (door de uitgevers van Der Entwickler).
Waar dan een DVD bij zit met Delphi 2005 Personal.
Dit is in mijn ogen een gemiste kans voor Borland, want juist nu is het belangrijk om de ontwikkelaars - alle ontwikkelaars, niet alleen de bestaande Delphi ontwikkelaars - te laten zien wat je met Delphi 2005 allemaal kunt doen.
En hoe je het nieuwe .NET ontwikkelplatform kunt leren met behulp van een fijne taal en omgeving.
Dit artikel gaat dan ook niet over Delphi 2005 deze keer, maar over Chrome: een andere Pascal-achtige compiler waarmee we .NET toepassingen kunnen bouwen. Geen Win32, maar wel een uitstekende manier om met een fijne taal de nieuwe .NET omgeving te leren kennen. En zonder kosten, want de command-line compiler van Chrome is gratis te downloaden en te gebruiken. Chrome komt uit de stal van RemObjects Software, die meer hulpmiddelen hebben gemaakt waar ik veel goede ervaringen mee heb (zoals RemObjects SDK, Data Abstract en Hydra). Behalve de gratis command-line compiler, is Chrome ook te koop als plugin voor de Microsoft Visual Studio.NET IDE, maar die gebruiken we hier dus niet.
Installatie
Als voorbereiding hebben we een machine nodig met Windows erop plus het Microsoft .NET Framework versie 1.1.
Nog niet de komende versie 2.0 (die op dit moment in beta is), alhoewel Chrome daar ook voor zal werken in de toekomst.
Wie met Mono werkt (.NET bovenop Linux in plaats van Windows) kan overigens ook een versie van de RemObjects Chrome command-line compiler voor Mono downloaden.
Ga naar www.chromesville.com, en klik op Downloads om de gratis command-line versie van de Chrome compiler te kunnen downloaden (op dit moment is dit build .171 van half mei 2005, een download van ongeveer 8 MB).
Je kunt daar ook wat informatie achterlaten (naam, e-mail, etc.) waarmee je geïnformeerd kan worden als er een nieuwe(re) versie verschijnt.
Dit is niet verplicht - je kan de gratis command-line versie downloaden zonder ook maar iets van jezelf op te geven of achter te laten.
Na de RemObjects Chrome Licence Agreement, volgt dan een pagina waarin je je naam en eventueel organisatie kan invoeren, en uiteindelijk de pagina die je vertelt waar RemObjects Chrome geïnstalleerd zal worden:
Na de installatie wordt de README.html in de default browser geladen.
Hier kun je de laatste bijzonderheden lezen, zoals het feit dat de gratis command-line compiler die ik in mei heb gedownload nog een preview versie is.
In de directory C:\Program Files\RemObjects\Chrome zijn na installatie al de helpfiles en enkele voorbeeldtoepassingen te vinden.
Om de werking van de command-line compiler helemaal uit te leggen, bouw ik nu echter een geheel nieuwe voorbeeldtoepassing.
Voorbeeldtoepassing
De RemObjects command-line compiler is te starten door gewoon Chrome aan te roepen (op de command-line).
Daarvoor moet dan wel de C:\Program Files\RemObjects\Chrome\bin directory in het pad staan, overigens.
De Chrome compiler kan .pas source files en .chrome project files bewerken.
Een .chrome project file is een XML bestand, dat niet eenvoudig zelf te maken is.
Maar een .pas source file is iets dat we natuurlijk wel zelf kunnen maken.
Met notepad zelfs als het moet.
De structuur van een Chrome toepassing verschilt een beetje van die van Delphi.
Een leeg skelet, dat je kan gebruiken als uitgangspunt voor nieuwe WinForms toepassingen, is als volgt:
namespace Skelet;
// Skelet application by Bob Swart (aka Dr.Bob - www.drbob42.com)
interface
uses
System.Windows.Forms,
System.Drawing;
type
SkeletForm = class(Form)
public
constructor;
class method Main;
end;
implementation
constructor SkeletForm;
begin
inherited Create;
Text := 'WinForms Skelet';
end;
class method SkeletForm.Main;
begin
try
Application.Run(SkeletForm.Create);
except
on E: Exception do
MessageBox.Show(E.Message);
end;
end;
end.
Er zijn een aantal verschillen ten opzichte van Delphi.
Zo begint een Chrome source bestand met het keyword namespace, en niet met unit, library of program.
Namespace lijkt nog het meest op unit, want ook hier hebben we een interface en een implementation sectie.
Compileren
Het compileren van het source bestand met de Chrome compiler gaat door een hoop opties mee te geven op de command-line.
Meestal maak ik daar dan ook een batch bestand van, zodat ik alle opties maar één keer hoef in te tikken (handig als er fouten in de source code zitten en/of je regelmatig wilt hercompileren).
Voor het SkeletForm is de batch file ten behoeve van het compileren met Chrome als volgt:
Chrome Skelet.pas /assemblyname:Skelet /type:winexe
/ref:$(Framework)\System.Windows.Forms.dll /ref:$(Framework)\System.Drawing.dll
Als eerste argument geven we het source bestand mee (Skelet.pas), de /assemblyname: optie wordt gebruikt om de naam van de executable op te geven.
De /type: optie kan de waarde exe, winexe of library krijgen.
Een "normale" exe (de default keuze) opent ook een console window, en dat is minder handig bij een WinForms toepassing.
Vandaar de /type:winexe optie.
Tot slot moet ik een tweetal referenties opgeven naar de System.Windows.Forms en System.Drawing .NET assemblies.
Iedere keer dat we een extra assembly nodig hebben, moeten we die via de /ref: optie toevoegen.
Behalve een batch file, kun je er ook voor kiezen om een .chrome bestand te maken. Zoals eerder gezegd, dat is een XML bestand dat met name voor de integratie met Visual Studio.NET wordt gebruikt. Er zijn echter een minimaal aantal knopen en attributen verplicht, zodat het niet echt moeilijk is er zelf eentje te maken. Zeker niet als ik al een voorbeeld voor de Skelet toepassing bij heb gevoegd. Die is als volgt:
<ChromeProject>
<ProjectOptions Name="Skelet">
<OutputType>winexe</OutputType>
<AssemblyName>Skelet</AssemblyName>
</ProjectOptions>
<References>
<Reference Assembly="$(Framework)\System.Drawing.dll" />
<Reference Assembly="$(Framework)\System.Windows.Forms.dll" />
</References>
<Files>
<File Filename="Skelet.pas" />
</Files>
</ChromeProject>
Er zijn drie secties in het XML bestand.
Binnen de root-knoop ChromeProject hebben we de ProjectOptions (daar hoef je hooguit de naam te wijzigen), de References (daar moet je eventueel extra assemblies toevoegen) en de Files (hier kun je extra source files toevoegen).
Als er precies één .chrome bestand in een directory is, hoef je nu allen nog maar de "Chrome" compiler aan te roepen, zonder verdere argumenten, want dan zal hij automatisch de gegevens uit het ene .chrome bestand halen.
Als er meer dan één .chrome bestand in een directory is moet je gewoon als argument het juiste .chrome bestand meegeven.
Is een stuk korter dan de lange command-line die ik in de batch bestanden moet opbouwen, en werkt net zo goed.
Componenten, Properties en Events
De volgende stap bestaat uit het toevoegen van componenten, en het geven van waarden aan properties en events.
Omdat we nog steeds alleen de command-line compiler gebruiken, is er geen designer of andere ontwerpomgeving waarin we de componenten op de WinForm kunnen plaatsen.
We zullen dit allemaal met de hand moeten doen.
Op zich niet zo'n probleem als het beperkt blijft, maar voor grotere toepassingen wordt dit al snel lastig zonder ook dit deel te automatiseren (dat zal ik in het laatste voorbeeld laten zien).
Als tweede toepassing zal ik de "beroemde" demo van de textbox, listbox en button nabouwen, waarbij de inhoud van de textbox naar de listbox wordt gekopieerd als we op de button klikken.
Dit was de eerste publieke demo van Delphi 1, en ook Kylix werd hiermee voor het eerst gedemonstreerd.
Eens kijken hoe het met Chrome gaat.
Als uitgangspunt neem ik het skelet project, en wijzig ik de naam SkeletForm in DemoForm, en voeg een drietal componenten toe aan de definitie van de DemoForm.
Behalve deze drie componenten, zullen we ook een event moeten toevoegen: de Click van de button.
Dit is de methode Button_Click, die onder .NET twee argumenten heeft: de sender van type Object, en een argument genaamt "e" van type EventArgs.
De definitie van het DemoForm is daarmee als volgt geworden:
namespace Demo;
// Demo application by Bob Swart (aka Dr.Bob - www.drbob42.com)
interface
uses
System.Windows.Forms,
System.Drawing;
type
DemoForm = class(Form)
public
constructor;
class method Main;
protected
method Button_Click(sender: System.Object; e: System.EventArgs);
private
Button1: Button;
TextBox1: TextBox;
ListBox1: ListBox;
end;
De implementatie van de Button_Click zal niet zo'n verrassing zijn, en is als volgt:
method DemoForm.Button_Click(sender: System.Object; e: System.EventArgs);
begin
ListBox1.Items.Add(TextBox1.Text);
end;
Wat wel anders is dan we gewend zijn is de constructor.
Delphi gebruikt al vanaf de allereerste versie naast de .pas bestanden ook speciale .dfm bestanden.
Daarin zijn de properties en hun waarden opgenomen.
Dit is echter niet de manier waarop Visual Studio.NET van Microsoft werkt (of Java), want daarin worden alle properties en instellingen in pure source code statements gedaan.
Dus als je van een component de omvang of tekst wijzigt, dan wijzig je in feite de source code van het project, en niet het .dfm bestand.
Overigens is de naam niet altijd hetzelfde: voor Delphi onder Windows is het .dfm (zowel 16- als 32-bit), voor het cross-platform CLX (Windows en Linux) is de uitgang .xfm, en onder .NET maken VCL for .NET toepassingen gebruik van .nfm bestanden.
constructor DemoForm;
begin
inherited Create;
Button1 := Button.Create;
Button1.Location := Point.Create(110, 10);
Button1.Size := System.Drawing.Size.Create(80, 24);
Button1.Text := 'Klik mij';
Button1.Click += Button_Click;
Controls.Add(Button1);
TextBox1 := TextBox.Create;
TextBox1.Location := Point.Create(10, 10);
TextBox1.Size := System.Drawing.Size.Create(80, 24);
Controls.Add(TextBox1);
ListBox1 := ListBox.Create;
ListBox1.Location := Point.Create(210, 10);
ListBox1.Size := System.Drawing.Size.Create(80, 100);
Controls.Add(ListBox1);
end;
Op zich niet zo moeilijk, alhoewel de Controls.Add nieuw is.
Die wordt gebruikt om de betreffende component toe te voegen aan de lijst met componenten van de "parent" (in dit geval het WinForm zelf).
Zonder de Controls.Add zou je het betreffende component niet zien, en dat is niet de bedoeling natuurlijk.
class method DemoForm.Main;
begin
try
Application.Run(DemoForm.Create);
except
on E: Exception do
MessageBox.Show(E.Message);
end;
end;
Compileren
Er is nog wel iets belangrijks dat makkelijk over het hoofd te zien is bij het compileren.
Teneinde het multi-cast event assignment te ondersteunen, hebben we nu ook een referentie naar de System.dll assembly nodig bij het compileren.
Als we die assembly niet toevoegen (of met de /ref: optie op de command-line, of via een Reference Assembly knoop in het .chrome bestand), dan krijgen we een compiler foutmelding.
In het .chrome bestand moeten we de volgende regel toevoegen:
<Reference Assembly="$(Framework)\System.dll" />Hierna compileert het source bestand zonder problemen, en levert de beroemde demo op:
Klikken op de Button voegt de tekst uit de TextBox toe aan de lijst in de ListBox. Net als we 10 jaar geleden met Delphi onder Windows konden. Tijd voor een laatste en wat complexere demo.
Memory Spel
Het memory spel is erg populair bij mijn kinderen, zeker nu ik ook een aantal implementaties voor de computer heb geschreven.
Zo heb ik in 2002 voor Delphi o.a. een migratie van VCL naar VCL for .NET geschreven die op het Borland Developer Network te lezen is.
Voor de Chrome implementatie bestaat geen VCL for .NET, dus moeten we het doen met de native WinForms.
De defintie van mijn MemoryForm is als volgt:
namespace Memory42; // Memory game by Bob Swart (aka Dr.Bob - www.drbob42.com) interface uses System.Windows.Forms, System.Drawing; const MaxX = 6; MaxY = 4; type MemoryForm = class(System.Windows.Forms.Form) const Caption = 'Sharpen your mind: Dr.Bob''s Game of Memory for .NET ('; public constructor; class method Main; protected method Buttons_Click(sender: System.Object; e: System.EventArgs); private Turns: Integer; First: Boolean; // eerste of tweede klik op een button? Buttons: array[1..MaxX,1..MaxY] of System.Windows.Forms.Button; end;Merk op dat ik voor de Buttons een 2-dimensionaal array gebruik, waardoor we de hoogte en breedte van het spel desgewenst kunnen aanpassen. En aangezien we toch altijd alle componenten in de constructor moeten aanmaken, kunnen we net zo goed een array daarvoor gebruiken (we zien toch niks tijdens design-time).
implementation
uses
System.Threading; // Sleep
constructor MemoryForm;
var
i,j,Tag: Integer;
X1,X2,Y1,Y2: Integer;
Rand: Random;
begin
inherited Create;
Rand := Random.Create;
Turns := 0;
Text := Caption + '-)';
First := True;
Size := System.Drawing.Size.Create(612, 436);
for i:=1 to MaxX do
begin
for j:=1 to MaxY do
begin
Buttons[i,j] := Button.Create;
Buttons[i,j].Location := Point.Create(6 + (600 div MaxX) * (i-1),
6 + (400 div MaxY) * (j-1));
Buttons[i,j].Size := System.Drawing.Size.Create((600 div MaxX) - 8,
(400 div MaxY) - 8);
Buttons[i,j].Tag := System.Object(1 + (j-1) * MaxX + (i-1) div 2);
Buttons[i,j].Text := '?';
Buttons[i,j].Font := System.Drawing.Font.Create('Comic Sans MS', 24);
Buttons[i,j].Click += Buttons_Click;
Controls.Add(Buttons[i,j])
end
end;
for i:=1 to 42 do
begin
X1 := 1+Rand.Next(MaxX);
X2 := 1+Rand.Next(MaxX);
Y1 := 1+Rand.Next(MaxY);
Y2 := 1+Rand.Next(MaxY);
Tag := Convert.ToInt16(Buttons[X1,Y1].Tag);
Buttons[X1,Y1].Tag := Buttons[X2,Y2].Tag;
Buttons[X2,Y2].Tag := System.Object(Tag)
end
end;
Merk op dat we nu ook de positie en omvang van de buttons goed moeten bepalen, en daarvoor gebruik ik de MaxX en MaxY om in een loopje de Location en Size properties van de buttons te zetten.
Het toevoegen van de Buttons_Click event handler gaat weer voor iedere Button hetzelfde via de += operator.
method MemoryForm.Buttons_Click(sender: System.Object; e: System.EventArgs);
var
i,j: Integer;
begin
if First then
begin
First := False;
Inc(Turns);
Text := Caption + Turns.ToString + ')';
Tag := (Sender as Button).Tag;
(Sender as Button).Text := Tag.ToString
end
else // tweede button
if (Sender as Button).Text = '?' then // niet eerder gebruikt
begin
First := True;
(Sender as Button).Text := (Sender as Button).Tag.ToString;
Update;
if (Sender as Button).Tag.ToString = Tag.ToString then // hetzelfde!
begin
for i:=1 to MaxX do
for j:=1 to MaxY do
if Buttons[i,j].Tag.ToString = Tag.ToString then
begin
Buttons[i,j].Enabled := False;
Buttons[i,j].Text := '['+Tag.ToString+']'
end
end
else // niet hetzelfde: weer "omdraaien"
begin
Thread.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;
In plaats van getallen die in de Tag property staan, kun je iedere button ook uitrusten met een plaatje.
Hiertoe kun je dan het getal van de Tag property gebruiken om een bepaald plaatje op te halen en te vertonen.
Dat laat ik ook over als oefening voor de lezer.
class method MemoryForm.Main;
begin
try
Application.Run(MemoryForm.Create);
except
on E: Exception do
MessageBox.Show(E.Message);
end;
end;
end.
Voor het compileren gebruik ik de volgende command-line:
Chrome Memory42.pas /assemblyname:Memory42 /type:winexe
/ref:$(Framework)\System.dll /ref:$(Framework)\System.Windows.Forms.dll
/ref:$(Framework)\System.Drawing.dll
Of het volgende .chrome project bestand:
<ChromeProject>
<ProjectOptions Name="Memory42">
<OutputType>winexe</OutputType>
<AssemblyName>Memory42</AssemblyName>
</ProjectOptions>
<References>
<Reference Assembly="$(Framework)\System.dll"/>
<Reference Assembly="$(Framework)\System.Drawing.dll" />
<Reference Assembly="$(Framework)\System.Windows.Forms.dll" />
</References>
<Files>
<File Filename="Memory42.pas" />
</Files>
</ChromeProject>
Het resultaat is een memory spel van 4 rijen en 6 kolommen (deze waarden zijn desgewenst aan te passen - ik laat het aan de lezer over om ze bijvoorbeeld via een .ini of .config file dynamisch in te laten stellen).
Dit is een niet geheel triviale toepassing voor .NET, die ik met Chrome heb kunnen compileren tot een native WinForms .NET toepassing.
Pascal en .NET samen, en dat met een gratis Chrome command-line compiler van RemObjects Software.
Een ideale manier om het .NET Framework en WinForms te leren kennen (alhoewel het werken zonder designer erg lastig kan zijn voor grotere projecten).
De complete source code voor de projecten is te downloaden, en binnenkort volgen meerdere artikelen en source code projecten van Chrome.
Referenties
Introducing the Chrome Command Line Compiler
RemObjects Chrome
Dr.Bob's Chrome Shines
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via .