Bob Swart (aka Dr.Bob)
Delphi 7 en IntraWeb 5.1

IntraWeb 5.1 in Delphi 7
IntraWeb versie 5.1.28 is nu te downloaden van de AToZedSoftware website. IntraWeb 5.1.x is overigens de laatste gratis update voor Delphi 7 gebruikers (lees de Delphi 7 Q/A voor meer info).

Om IntraWeb versie 5.1 te installeren in Delphi 7 moet je wel een aantal stappen doorlopen, die ik hier even kort op een rijtje zet (het is helaas niet zo makkelijk als het gebruik van IntraWeb zelf).

Als je hiermee klaar bent en Delphi 7 weer opstart krijg je eerst nog een foutmelding (ik kreeg hem op mijn twee ontwikkelmachines waar Delphi 7 en - nu - IntraWeb 5.1 op staat):

Als je op Yes klikt krijg je de melding nog een keer te zien, en iedere keer als je een IntraWeb 5.1 project probeert te maken of openen. Uiteraard moet je hier No zeggen, want de package die niet gevonden kan worden hoort bij IntraWeb 5.0 voor Delphi 7 die we verwijderd hebben (maar de verwijzing naar deze package is kennelijk door de Delphi 7 Installer niet opgeruimd).

IntraWeb 5.1
Er zijn best wel een aantal zaken veranderd in IntraWeb 5.1 (vergeleken met versie 5 die bij Delphi 7 "in de doos" zit). Het meest zichtbaar zijn de veranderingen als we gewoon even een nieuwe IntraWeb 5.1 toepassing gaan bouwen in Delphi 7 Enterprise, namelijk een spelletje "WebMemory" (zijn mijn kinderen dol op). Het leuke is dat het spelletje makkelijk te spelen is, maar moeilijk te maken als web toepassing, omdat er nogal wat ("state") informatie bewaard moeten worden tijdens de zetten: de hoeveelheid kaartjes, de kaartjes die nog in het spel zijn, de afbeelding (plaatje of nummer) wat erop staat, etc. IntraWeb biedt ons de mogelijkheid om met state information om te gaan zoals we dat in een "normale" Delphi toepassing doen: haast zonder erbij na te denken...
Dus, start Delphi 7, doe File | New - Other, ga naar de IntraWeb tab en zie tot je schrik dat er nog maar twee icoontjes over zijn:

De vorige versie had nog voor ieder project target een eigen icoontje, en toen kreeg je nog de trieste Delphi Wizard die je een template project uit de Object Repository liet installeren. Dat is nu verleden tijd: voor een nieuw IntraWeb project gebruik je de IntraWeb Application Wizard, en voor een nieuwe IntraWeb Form gebruik je de New Form dialoog.
De IntraWeb Application Wizard bevat nu alle verschillende mogelijkheden op een rijtje:

De optie UseISAPIThreadPool is uiteraard alleen te kiezen bij een ISAPI Extension target. Let ook op de speciale Create Main form as 3.2 optie - deze wijst op een andere grote uitbreiding van IntraWeb 5.1: ondersteuning voor PDA's (en Netscape 4) in de vorm van HTML 3.2.

HTML 3.2: PDA
We kunnen met IntraWeb 5.1 dus daadwerkelijk toepassingen schrijven voor PDA's doordat IntraWeb daar HTML 3.2 voor genereert (zonder JavaScript of Cascading Style Sheets). De New Form Wizard bevat ook de mogelijkheid om een nieuwe Application Form (een Form voor gebruik in Application Mode) of Page Form (voor in Page Mode) te maken, zowel normal (HTML 4) als voor HTML 3.2.

Uiteraard zijn er ook speciale "3.2" componenten nodig om te gebruiken op de IntraWeb 3.2 forms. IntraWeb 5.1 heeft dan ook twee extra tabs vergeleken met IntraWeb versie 5, namelijk de IW Standard 3.2 en IW Data 3.2:

De componenten op de IW Standard 3.2 en IW Data 3.2 tabs kunnen beschouwd worden als subset of "light" (HTML 3.2) versies van de "volle" (HTML 4) versies van de componenten op de IW Standard en IW Data tabs.

Als je per ongeluk een "normaal" IntraWeb component op een 3.2 form wil zetten krijg je een melding (waarschuwing) dat je een HTML 4 component op een 3.2 form neerzet, wat eigenlijk niet de bedoeling is. Omdat de IW Standard 3.2 en IW Data 3.2 tabs niet alle componenten bevatten die op de normale IW Standard en IW Data tabs zitten, lijkt het erop dat ontwikkelen voor PDA's moeilijker (en beperkter) is dan ontwikkelen voor normale web targets. Dat is niet zo gek natuurlijk omdat PDA's eigenlijk behoorlijk gelimiteerde devices zijn - we mogen al blij zijn dat IntraWeb überhaubt deze mogelijkheid biedt.

Ik wil mijn WebMemory spelletje echter gewoon in een browser laten spelen, dus we gaan nu weer verder met "gewone" targets (met een normale IWForm en User Session).

Een klein maar lastig probleempje van de nieuwe IntraWeb Application Wizard is het feit dat bij het starten van een nieuw project hiermee, het openstaande project (inclusief ProjectGroup) eerst wordt afgesloten. Het is derhalve niet meer mogelijk om een tweede IntraWeb target aan een bestaande ProjectGroup toe te voegen - je moet dit later met de hand toevoegen. Dit is gemeld, en wordt onderzocht door de makers van IntraWeb (zal wel opgelost zijn in een toekomstige versie, hoop ik).

WebMemory Spel
Mijn spelletje WebMemory wil ik slim genoeg maken om nieuwe spelletjes op te starten, waarbij alleen het aantal knoppen (kaartjes) horizontal en verticaal opgegeven moet worden. Dit doe ik door in de gegenereerde ServerController.pas unit, de velden X en Y op te nemen in de TUserSession class.

  TUserSession = class(TComponent)
  public
    X,Y: Integer;
  end;

In de unit met mijn IntraWeb page kan in nu twee TIWEdit componenten (genaamd IWEditX en IWEditY) en een TIWButton (genaamd IWStartButton) neerzetten. Door op de Start button te klikken wordt een nieuw spel gestart, en dat gaat met de volgende code:

  procedure TIWMemoryForm.IWStartButtonClick(Sender: TObject);
  begin
    UserSession.X := StrToIntDef(IWEditX.Text,6);
    UserSession.Y := StrToIntDef(IWEditY.Text,4);
    with TIWMemoryForm.Create(Owner) do Show;
    Release
  end;

Wat ik hier doe is dus de opgegeven waarden van X en Y ophalen uit de twee TIWEdits en in de UserSession stoppen. Vervolgens creëer ik het TIWMemoryForm, laat dat zien (met Show) en gooi het huidige IWForm weg (met Release).
Merk op dat ik dus in feite een nieuwe instantie van het IWForm start, echter nu met nieuwe waarden voor X en Y.

Tijdens design-time ziet mijn IntraWeb 5.1 Application Form er als volgt uit:

CreateBoard
Dit is alleen maar de "bovenkant" van het IWForm, omdat ik de knoppen (de memory kaartjes) iedere keer dynamisch moet creëren. Dat laatste doe ik in de CreateBoard routine die vanuit de FormCreate wordt aangeroepen, als volgt:

  procedure TIWMemoryForm.IWAppFormCreate(Sender: TObject);
  begin
    IWEditX.Text := IntToStr(UserSession.X);
    IWEditY.Text := IntToStr(UserSession.Y);
    Randomize;
    Turns := 0;
    CreateBoard(UserSession.X,UserSession.Y)
  end;

  type
    TArrayArrayButton = Array of Array of TIWButton;

  procedure TIWMemoryForm.CreateBoard(X, Y: Integer);
  var
    i,j: Integer;
    Button: TArrayArrayButton;
  begin
    SetLength(Button, X);
    for i:=0 to Pred(X) do
    begin
      SetLength(Button[i], Y);
      for j:=0 to Pred(Y) do
      begin
        Button[i,j] := TIWButton.Create(Self);
        Button[i,j].Parent := Self;
        Button[i,j].Left := 6 + 100 * i;
        Button[i,j].Width := 100 - 8;
        Button[i,j].Top := 6 + 100 * (j+1);
        Button[i,j].Height := 100 - 8;
        Button[i,j].Font.FontName := 'Comic Sans MS';
        Button[i,j].Font.Size := 26;
        Button[i,j].Font.Style := [fsBold];
        Button[i,j].Tag := 1 + (j * X + i) div 2;
        Button[i,j].Caption := '?';
        Button[i,j].OnClick := FirstButtonClick;
      end
    end;
    Shuffle(Button)
  end;

De routine CreateBoard gebruikt een tweedimensionaal dynamisch array van TIWButtons, en geeft ze allemaal een ? als caption, en een getal tussen de 1 en (X*Y)/2 als waarde voor de Tag property. We doen WebMemory zonder plaatjes (dat laat ik over als oefening voor de lezer), en gebruiken alleen maar getallen - de waarde van Tag dus. Wie het iets moeilijker wil kan het proberen met getallen van meer cijfers (of uit de verzameling 66..69, 76..79, 86..89, 96..99 waarbij de getallen wat meer op elkaar lijken).

Shuffle
De Shuffle method trekt 1001 keer vier random getallen (twee X,Y coördinaten) en wisselt deze twee "kaartjes" van plek. Dit blijkt in praktijk voldoende te zijn om het spel voldoende door elkaar te husselen.

  procedure TIWMemoryForm.Shuffle(Button: TArrayArrayButton);
  var
    i: Integer;
    X,Y: Integer;
    X1,X2,Y1,Y2: Integer;
  begin
    X := Length(Button);
    Y := Length(Button[0]);
    for i:=1 to 1001 do
    begin
      X1 := Random(X);
      X2 := Random(X);
      Y1 := Random(Y);
      Y2 := Random(Y);
      Tag := Button[X1,Y1].Tag;
      Button[X1,Y1].Tag := Button[X2,Y2].Tag;
      Button[X2,Y2].Tag := Tag
    end
  end;

FirstButtonClick
In de CreateBoard methode wordt van iedere IWButton de OnEvent handler verwezen naar de FirstButtonClick methode. In deze methode halen we de Tag op van de IWButton waar op geklikt is (en stoppen dat in de Tag property van het IWForm zelf), vervangen we het vraagteken door de waarde van deze Tag, en laten we vervolgens van alle andere (overgebleven) IWButton de OnClick event handler wijzen naar SecondButtonClick.

  procedure TIWMemoryForm.FirstButtonClick(Sender: TObject);
  var
    i: Integer;
    IWSender: TIWButton;
  begin
    IWSender := (Sender as TIWButton);
    Tag := IWSender.Tag;
    IWSender.Caption := IntToStr(Tag);
    IWSender.Tag := -IWSender.Tag;
    for i:=0 to Pred(ComponentCount) do
      if Components[i] is TIWButton then
        with (Components[i] as TIWButton) do
          if Caption = '?' then OnClick := SecondButtonClick
          else
            if Caption <> 'Start' then OnClick := nil
  end;

SecondButtonClick
In de SecondButtonClick vergelijken we de Tag van de IWButton waar op geklikt is met de Tag property van het IWForm. Als die gelijk zijn, dan hebben we twee dezelfde IWButton gehad, en kunnen ze uit het spel verwijderd worden (lees: onzichtbaar gemaakt worden).
Als de Tag property van de IWButton niet gelijk is aan die van het IWForm, dan laten we alleen maar de waarde van de Tag property op de IWButton zien, en zetten de Interval property van de IWTimer op 1000 (één seconde). In beide gevallen zetten we de Enabled property van de IWTimer op True, zodat er een vervolgpagina gegenereerd zal worden waarin ofwel de beide IWButton met gelijke Tag onzichtbaar zijn gemaakt, of deze IWButton (met ongelijke Tag) weer omgedraaid zijn - dus weer een ? als caption hebben.

  procedure TIWMemoryForm.SecondButtonClick(Sender: TObject);
  var
    i: Integer;
    IWSender: TIWButton;
  begin
    IWSender := (Sender as TIWButton);
    Inc(Turns);
    Title := Format(_Title,[Turns]);
    IWSender.Caption := IntToStr(IWSender.Tag);
    if IWSender.Tag = Tag then
    begin
      for i:=0 to Pred(ComponentCount) do
        if Components[i] is TIWButton then
        begin
          if (Components[i] as TIWButton).Caption <> 'Start' then
            (Components[i] as TIWButton).OnClick := nil;
          if -(Components[i] as TIWButton).Tag = Tag then
            (Components[i] as TIWButton).Tag := Tag
        end;
      IWTimer1.Interval := 1
    end
    else
    begin
      for i:=0 to Pred(ComponentCount) do
        if Components[i] is TIWButton then
          if (Components[i] as TIWButton).Caption <> 'Start' then
            (Components[i] as TIWButton).OnClick := nil;
      IWSender.Tag := -IWSender.Tag;
      IWTimer1.Interval := 1000
    end;
    IWTimer1.Enabled := True
  end;

FirstButtonClick
Als de OnTimer event handler van de IWTimer vuurt moeten we als eerste natuurlijk de Enabled property van de IWTimer weer op False zetten (anders vuurt hij na een seconde nog een keer). Vervolgens moeten we alle IWButton doorlopen die op het IWForm staan

  procedure TIWMemoryForm.IWTimer1Timer(Sender: TObject);
  var
    i: Integer;
  begin
    IWTimer1.Enabled := False;
    for i:=0 to Pred(ComponentCount) do
      if Components[i] is TIWButton then
        with (Components[i] as TIWButton) do
          if Caption <> '?' then
          begin
            if Tag < 0 then
            begin
              Tag := -Tag;
              Caption := '?';
              OnClick := FirstButtonClick
            end
            else Visible := Caption = 'Start'
          end
          else
            OnClick := FirstButtonClick
  end;

Bij de default omvang van vier rijen van zes kaartjes ziet het WebMemory spel er uiteindelijk als volgt uit (het veranderen van de achtergrond en kleuren van de IWButton is niet beschreven in dit artikel, maar terug te vinden in de source code):

Het WebMemory spel heb ik als stand-alone executable en als ISAPI DLL getest (met dank aan Erik en Natasha die ook enkele uren wilden spelen), en lijkt prima te werken. Spelen van WebMemory over het internet kan toch wat kleine problemen opleveren: een andere testgebruiker meldde dat de vertraging van één seconde voordat de kaartjes weer worden opgedraaid (de IWButton weer een ? als caption krijgen) wel erg kort is als er een niet al te snelle internet verbinding is: nog voor de tweede pagina er helemaal staat wordt deze al vervangen door de nieuwe pagina met alleen maar de vraagtekens. Als betere oplossing zou een "Continue" knop uitkomst bieden, waarbij de gebruiker zelf kan aangeven dat de kaartjes weer opgedraaid moeten worden.
Als laatste meld ik nog dat WebMemory in de huidige staat niet in PDA "3.2" uitvoering geschikt is, omdat de IWTimer niet beschikbaar is voor PDA's. Ook hiervoor is een Continue knop waarschijnlijk de beste oplossing...

Meer Informatie
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via . Oh ja, doe geen moeite om de IntraWeb manual (PDF of HLP) van de AtoZedSoftware website te downloaden, want IntraWeb 5.1 bevat zelf documentatie die meer up-to-date is (alhoewel het nog steeds vrij karig is).
Wie meer mogelijkheden wil ervaren met IntraWeb 5.1 en Delphi 7, zou zeker een bezoek aan mijn Delphi IntraWeb Clinic op kunnen overwegen (ik ben een IntraWeb Authorized Trainer).


This webpage © 1999-2006 by webmaster drs. Robert E. Swart (aka - www.drbob42.com). All Rights Reserved.