《Delphi從入門到精通》第21章 第二部分
創建IntraWeb應用程序
創建IntraWeb應用程序,有很多組件可用。不妨看一下Delphi組件板中的IW Standard頁,會給您留下深刻的印象,從簡單的按鈕、復選框、單選框、編輯框、列表框到迷人的樹形控件、菜單、計時器、表格和鏈接應有盡有。我不想舉例描述每個組件的用法,只想通過幾個例子,闡述IntraWeb的體系結構,當然順便也會介紹用到的組件。
我創建了一個例程(叫IWTree),演示了IntraWeb的菜單和樹形控件的用法,同時也說明了如何動態創建組件。IntraWeb菜單通過引入常規Delphi菜單內容來工作的,這很容易,只需簡單地把AttachedMenu設置成Tmenu組件即可:
object MainMenu1: TMainMenu
  object Tree1: TMenuItem    object ExpandAll1: TMenuItem    object CollapseAll1: TMenuItem    object N1: TMenuItem    object EnlargeFont1: TMenuItem    object ReduceFont1: TMenuItem  end  object About1: TMenuItem    object application1: TMenuItem    object TreeContents1: TMenuItem  endendobject IWMenu1: TIWMenu
  AttachedMenu = MainMenu1  Orientation = iwOHorizontalend菜單項運行時處理OnClick事件,是以鏈接形式實現的。看一下圖21.3,這是展示菜單的例程的運行效果。例程中的另外一個組件是樹形控件,在例子中預置了很多節點。該組件通過很多javaScript代碼來實現在瀏覽器中(不需要調用服務器代碼)直接控制樹形控件節點的展開和收疊。同時,菜單中還有控制樹形控件節點展開和收疊以及控制字體大小的菜單項。下面列出其中兩個事件代碼:
PRocedure TformTree.ExpandAll1Click(Sender: TObject);
var  i: Integer;begin  for i := 0 to IWTreeView1.Items.Count - 1 do    IWTreeView1.Items [i].Expanded := True;end;   procedure TformTree.EnlargeFont1Click(Sender: TObject);
begin  IWTreeView1.Font.Size := IWTreeView1.Font.Size + 2;end;
圖21.3 演示菜單、樹形控件和動態創建組件的例程
感謝IntraWeb提供了如同標準Delphi VCL組件的特性,使代碼易讀,也容易理解。
 
例程中的菜單有兩個子菜單稍微復雜一點。第一個是顯示應用程序ID,也就是程序執行時的會話期ID。該標志可以通過全局對象WebApplication的AppID屬性獲得。第二個子菜單是Tree Contents,它可以把樹形控件的一級節點標題和子節點數目清單列出。值得注意的是,這些信息顯示在一個運行時才創建的組件memo里(見圖21.3),這一點與在一個VCL應用程序里面做同樣的事情非常相似。
procedure TformTree.TreeContents1Click(Sender: TObject);
var  i: Integer;begin  with TIWMemo.Create(Self) dobegin
    Parent := Self;    Align := alBottom;    for i := 0 to IWTreeView1.Items.Count - 1 do      Lines.Add (IWTreeView1.Items [i].Caption + ' (' +        IntToStr (IWTreeView1.Items [i].SubItems.Count) + ')');  end;end; 提示:請注意IntraWeb的alignment屬性和VCL組件的alignment非常相似。比如程序菜單的alignment屬性是alTop,而tree組件則是alClient,此外動態創建的memo的alignment屬性被設為alBottom。作為替代方法,可以使用anchors(同樣VCL中也有):可以創建一個bottom-right按鈕,或者在頁面中間的組件,只需把組件的四個anchors全設為true。請看下面關于該技術的例程。開發多頁面應用程序
目前所講的例程都只有一個頁面,下面我們來創建IntraWeb程序的第二個頁面。其實,就是這種情況,IntraWeb開發工具也與標準Delphi(或Kylix)十分相似,而與其他互聯網開發工具十分不同。下面的例子可以通過IntraWeb向導自動產生源代碼,接著研究我們關心的這些代碼。
我們從頭看,例程IWTwoForms的主窗體演示了IntraWeb表格特性。這是一個強大的組件,她產生的HTML表格既可放入文本又可放入其他組件。在本例中,程序開始運行時,填充表格的內容(在主窗體的OnCreate事件中處理):
procedure TformMain.IWAppFormCreate(Sender: TObject);
var  i: Integer;  link: TIWURL;begin// set grid titles
  IWGrid1.Cell[0, 0].Text := 'Row';  IWGrid1.Cell[0, 1].Text := 'Owner';  IWGrid1.Cell[0, 2].Text := 'Web Site';// set grid contents
  for i := 1 to IWGrid1.RowCount - 1 dobegin
    IWGrid1.Cell [i,0].Text := 'Row ' + IntToStr (i+1);IWGrid1.Cell [i,1].Text := 'IWTwoForms by Marco Cantù';
    link := TIWURL.Create(Self);    link.Text := 'Click here';    link.URL := 'http://www.marcocantu.com';    IWGrid1.Cell [i,2].Control := link;end;
end;上面這段代碼的運行結果見圖21.4,除了輸出外,還有幾件事值得注意。首先,表格使用了Delphi的anchors屬性(全設為false)來使表格始終處在頁面中間,即使用戶改變了瀏覽器窗口大小。其次,我在第三列中加入了一個IWURL組件,當然也可以放其他組件(包括按鈕和編輯框)。

圖21.4 例程IWTwoForms使用的表格組件嵌入文本和IWURL組件
第三,也是最值得研究的是IWGrid組件轉換成了帶框架和不帶框架的HTML表格。這是表格中的一行HTML代碼片斷:
<tr>  <td valign="middle" align="left" NOWRAP>    <font style="font-size:10pt;">Row 2</font>  </td>  <td valign="middle" align="left" NOWRAP><font style="font-size:10pt;">IWTwoForms by Marco Cantù</font>
  </td>  <td valign="middle" align="left" NOWRAP>    <font style="font-size:10pt;"></font>    <a href="#" onclick="parent.LoadURL('http://www.marcocantu.com')"      id="TIWURL1" name="TIWURL1"      style="z-index:100;font-style:normal;font-size:10pt;text-decoration:none;">      Click here</a>  </td></tr>提示:在上面的代碼清單中,我們注意到URL是通過Javascript來激活鏈接的,而不是直接的超鏈接。由于IntraWeb允許客戶端所有動作,比如確認、檢查和提交,而這些動作都依賴于JavaScript。例如,如果你把一個組件的Required設成true,則該組件如果沒有任何數據就不能提交,此時如果提交將會看到一個JavaScript錯誤信息(使用組件的FriendlyName屬性來定制的消息框)。本例的核心特性是它顯示第二個頁面的能力。為了實現這一點,首先需要給程序添加一個IntraWeb頁,方法是單擊File->New->Other…,啟動Delphi的New Items對話框,翻到IntraWeb頁,選擇Application Form,單擊“Ok”按鈕完成添加工作。接著向該窗體上放一些組件,然后在主窗口中放置一個按鈕或其他控件用來顯示第二個窗體:
procedure TformMain.btnShowGraphicClick(Sender: TObject);
begin  anotherform := TAnotherForm.Create(WebApplication);  anotherform.Show;end;即使程序調用了Show方法,也會被看成是調用了ShowModal。這是因為IntraWeb把頁面當成堆棧來處理。最后顯示的頁面在棧頂,同時顯示在瀏覽器上。如果關閉該頁(隱藏或銷毀),就會顯示該頁的前一頁。在本例中,第二頁的關閉是通過調用Release方法,該方法在VCL程序中是結束正在運行的窗體的恰當方法。你也可以隱藏第二個窗體然后再顯示它從而避免每次都重建窗體的實例。
警告:在例程中的主窗體上放置了一個Close按鈕,但該按鈕沒有調用Release方法,而是調用了WebApplication對象的Terminate方法。該方法可以傳遞輸出信息,如WebApplication.Terminate(‘goodbye’);例程中使用了另一種替換方法:TerminateAndRedirect。
現在已經知道如何創建帶有兩個窗體的IntraWeb程序,接著我們簡要地考查一下IntraWeb是如何創建主窗體的。當創建一個新程序時,在項目文件里有由IntraWeb向導產生的相關代碼:
begin  IWRun(TFormMain, TIWServerController);這一行不同于Delphi的標準項目文件,因為它調用了一個全局函數而不是應用全局對象的方法。函數的兩個參數分別是主窗體的類和IntraWeb控制器的類。該控制器能夠處理會話期和許多特性,稍后就會介紹。
例程中的第二個窗體顯示了IntraWeb另外一個有趣的特性:圖形支持。該窗體有一個顯示雅典娜神像的圖形組件,這是通過把一個位圖裝載進一個IWImage組件中實現的:Intraweb把這個位圖轉換成JPEG格式,并存到創建于程序所在文件夾內的cache文件夾中,然后再返回該JPEG文件的引用。其相應HTML代碼如下:
<img src="/cache/JPG1.tmp" name="IWIMAGE1" border="0" width="153" height="139"> 該例程使用的IntraWeb另外一個特性是用戶可以用鼠標點擊圖像,并通過運行服務器端代碼來實現修改圖像的功能。在本例中,修改的結果是畫綠色的小圓圈。

代碼如下:
procedure Tanotherform.IWImage1MouseDown(ASender: TObject;
  const AX, AY: Integer);var  aCanvas: TCanvas;begin  aCanvas := IWImage1.Picture.Bitmap.Canvas;  aCanvas.Pen.Width := 8;  aCanvas.Pen.Color := clGreen;  aCanvas.Ellipse(Ax - 10, Ay - 10, Ax + 10, Ay + 10);end;警告:繪制操作是發生在位圖的畫布(canvas)上。不要使用Image組件的畫布(在VCL組件Image中是可以這樣做的),也不要使用JPEG圖像,否則不是沒有響應就是出運行錯誤。會話期管理
注:session就是通話、話路。在打電話的時候,通常情況下每一對用戶擁有一個話路,否則就會“竄線”了。在本章中,Session就是指客戶端的一個用戶和服務器交互的話路,或者稱為交互通道。由于其他書籍中把Session譯作“會話期”,這里沿用該譯法。——譯者。
如果你有一些Web編程經驗,就會知道會話期管理是一個復雜的話題。IntraWeb提供預定義的會話期管理并且簡化使用會話期的方法。如果在一個指定的窗體中需要會話數據,需要做的是給該窗體加一個域。IntraWeb窗體和組件會為每一個會話期創建一個實例。比如,在例程IWSession中,我給窗體添加一個域叫做FormCount,為了對比,我又在全局單元里聲明一個全局變量GlobalCount,這個變量會被程序的所有實例所共享。
為了加強對會話數據的控制同時讓多個窗體共享它,可以定制TuserSession類,該類是IntraWeb應用程序向導在ServerController單元中自動產生的。在例程IWSession中,我是這樣定制的:
type  TUserSession = class  public    UserCount: Integer;  end;IntraWeb 為每個新的會話期創建對象的實例,參閱ServerController單元中TIWServerController類的IWServerControllerBaseNewSession方法:
procedure TIWServerController.IWServerControllerBaseNewSession(
  ASession: TIWApplication; var VMainForm: TIWAppForm);begin  ASession.Data := TUserSession.Create;end;在代碼中,會話期對象可以通過訪問RwebApplication這個全局變量的Data域來引用,這個變量通常用來訪問當前用戶的會話期。
提示:RwebApplication是線程變量,在IWInit單元中定義。她提供了訪問會話期數據的線程安全方法:在多線程環境下訪問它需要倍加小心。該變量可以在窗體和控件之外使用(基于線程的),這就是為什么主要用在數據模塊、全局程序和非IntraWeb類內的原因。
此外,默認的ServerController單元提供一個可用的輔助函數:
function UserSession: TUserSession;
begin  Result := TUserSession(RWebApplication.Data);end;因為大多數代碼已經自動產生了,像下面從例程IWSession中提取的代碼那樣,只需給TuserSession類添加數據,就可用通過UserSession函數簡單地應用了。在例程中,單擊按鈕,程序會累加幾個計數器(一個全局變量,兩個會話期指定的)并通過標簽顯示它們的值:
procedure TformMain.IWButton1Click(Sender: TObject);
begin  InterlockedIncrement (GlobalCount);  Inc (FormCount);     Inc (UserSession.UserCount);     IWLabel1.Text := 'Global: ' + IntToStr (GlobalCount);  IWLabel2.Text := 'Form: ' + IntToStr (FormCount);  IWLabel3.Text := 'User: ' + IntToStr (UserSession.UserCount);end;注意,程序通過調用Windows的InterlockedIncrement來避免被多線程所共享的全局變量發生訪問沖突。也可以通過使用critical section或者是TidThreadSafeInteger(見于IdThreadsafe單元)來避免這種情況。
圖21.5顯示了程序的輸出(通過兩個不同的瀏覽器創建兩個會話期),程序還有一個復選框,用來激活計時器。聽起來挺不可思議的,但實際上在IntraWeb程序中,計時器就和Windows中的計時器一樣工作。當計時器的時間間隔到期時,相應的代碼就會被執行。在網頁中,這意味著觸發JavaScript代碼來刷新頁面:
IWTIMER1=setTimeout('SubmitClick("IWTIMER1","", false)',5000);

圖21.5、運行在兩個不同瀏覽器中的IWSession例程與WebBroker和WebSnap整合
到目前為止,只講了如何創建stand-alone模式的IntraWeb應用程序。當你需要開發IIS或是Apache下的IntraWeb動態鏈接庫時,情形也基本一樣。但是,如果你想用IntraWeb技術來拓展已有的WebBroker(或是WebSnap)程序,情況就不一樣了。
兩種技術的橋梁是IWPageProducer組件。該組件像其他頁生成器組件一樣依附于WebBroker的action,同時可以使用一個特殊的事件來創建并獲得一個IntraWeb窗體:
procedure TWebModule1.IWPageProducer1GetForm(ASender: TIWPageProducer;
  AWebApplication: TIWApplication; var VForm: TIWPageForm);begin  VForm := TformMain.Create(AWebApplication);end;僅一行代碼,就能實現IntraWeb頁嵌入WebBroker程序,在例程CgiIntra中也是如此。IWModuleController對IntraWeb支持提供核心服務。每個IntraWeb項目都必須有這種組件才能正確地工作。
警告:Delphi7中發行的IntraWeb的IWModuleController組件和Delphi的Web App Debugger有沖突,但問題已經解決,可以免費更新。
這是例程的web module窗體摘要:
object WebModule1: TWebModule1
  Actions = <    item      Default = True      Name = 'WebActionItem1'      PathInfo = '/show'      OnAction = WebModule1WebActionItem1Actionend
item
      Name = 'WebActionItem2'      PathInfo = '/iwdemo'      Producer = IWPageProducer1    end>  object IWModuleController1: TIWModuleController  object IWPageProducer1: TIWPageProducer    OnGetForm = IWPageProducer1GetFormend
end因為這是一個頁模式的CGI程序,所以沒有會話期管理。此外,頁面的組件狀態不能像標準IntraWeb程序那樣通過事件處理來自動更新。為了達到同樣的效果,需要寫一寫特殊代碼來進一步處理HTTP請求的參數。僅從這樣一個簡單例程就能看出頁模式沒有程序模式那么自動化,不過,頁模式更靈活。尤其值得說的是,頁模式給WebBroker和WebSnap程序增加了可視化設計的能力。
控制布局
例程CgiIntra還展示了另外一個非常有趣的IntraWeb技術:基于HTML的布局控制(頁模式整合WebBroker和布局控制沒有什么關系,因為在程序模式中也有布局控制,我只是為了省事才用一個例程來說明這兩種技術的)。程序編譯后的結果頁面就是在設計時放到窗體上的一系列組件的映射,可以通過修改組件屬性改變頁面外觀。一個頁面上有很多內容,如文本框、按鈕和圖片等,這些內容如何分布,如何控制尺寸和位置?
解決的辦法是使用IntraWeb的布局管理器。在IntraWeb程序中,總是要用到布局管理器。默認的布局管理器是IWLayoutMgrForm,另外兩個是IWTemplateProcessorHTML和IWLayoutMgrHTML,前者使用外部的HTML模版文件,后者內嵌HTML。
IWLayoutMgrHTML組件包括一個功能強大的HTML編輯器,在這里你可以像放置普通HTML元素那樣嵌入IntraWeb組件(在外部HTML編輯器里,你必須手動實現)。此外,當你從編輯器中選擇一個IntraWeb組件時(雙擊IWLayoutMgrHTML組件即可啟動該編輯器),你可以使用對象觀察器來修改組件屬性。如圖21.6,IntraWeb的HTML布局編輯器是一個工具強大的可視化HTML編輯器,產生的HTML代碼可以在另外一頁看到(Source頁)。

圖21.6:  IntraWeb的 HTML 布局編輯器
在產生的HTML代碼中,定義了頁的結構。組件是通過特殊標記:大括號來標識的,如下:
<P> {%IWLabel1%} {%IWButton1%}</P>提示:當你使用HTML時,組件就不使用絕對位置定位了,而是由HTML而定。因此,此時的窗體僅僅是個組件容器,因為窗體中組件的位置和大小被忽略了。
不管怎么說,布局管理器總是能夠滿足程序在瀏覽器中運行時的外觀需求。
新聞熱點
疑難解答