使用IntraWeb进行Web编程(二)

类别:Delphi 点击:0 评论:0 推荐:

《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

  end

end

object IWMenu1: TIWMenu

  AttachedMenu = MainMenu1

  Orientation = iwOHorizontal

end

菜单项运行时处理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) do

  begin

    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。请看下面关于该技术的例程。 Writing Multipage Applications

开发多页面应用程序

目前所讲的例程都只有一个页面,下面我们来创建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 do

  begin

    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 = WebModule1WebActionItem1Action

    end

    item

      Name = 'WebActionItem2'

      PathInfo = '/iwdemo'

      Producer = IWPageProducer1

    end>

  object IWModuleController1: TIWModuleController

  object IWPageProducer1: TIWPageProducer

    OnGetForm = IWPageProducer1GetForm

  end

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而定。因此,此时的窗体仅仅是个组件容器,因为窗体中组件的位置和大小被忽略了。

不管怎么说,布局管理器总是能够满足程序在浏览器中运行时的外观需求。

本文地址:http://com.8s8s.com/it/it5105.htm