真正所见即所得的Delphi Web开发利器-
IntraWeb开发指南[下载例子程序]
作者:陈省
历史回顾
从Delphi3起,Borland提供了最早的Web开发组件WebBroker,WebBroker应该说有很多天然的缺陷:
1. 首先WebBroker提供的Web开发组件相当少,只有PageProducer和QueryTableProducer几个很少的组件。
2. 组件不支持所见即所得的开发方式,所有Html页面的设计都要通过FrontPage和DreawWeaver等网页开发工具来完成。
3. 动态网页实现的支持也很弱,只支持通过透明标签OnHtmlTag和WebModule的OnAction事件来实现。
4. 不提供对Session的封装,完全需要自己通过Cookie来实现。
5. 调试非常不方便,需要通过安装第三方的WebServer,如IIS,并且要配置一系列注册表才能完成调试,而且在ISAPI方式下调试更是一场恶梦,总是要不停的重新启动WebServer。
6. 所有的业务逻辑只能放在一个单元中来实现,不容易实现多人开发,我曾经写过的一个WebServer,一个单元里塞下了1万多行代码,维护非常不方便,不适合编写大型的WebServer。
从Delphi5开始,Borland提供了一组新的InternetExpress组件,这套组件同WebBroker的特点:
1. 界面控件增加了许多,而且组件是基于接口来实现的,非常容易扩展,当时我还为InternetExpress写过20多个组件。
2. 提供了初步的所见即所得的功能,但是有一个很大的问题是,页面布局通过LayoutGroup组件来进行格式化,界面的布局非常僵化,不容易实现灵活美观的界面,同时界面设计的修改也仍然很麻烦,需要通过Web Page Designer的树视图和列表视图维护但仍然不能象开发一般程序那样拖放几个控件就能完成界面设计。
3. 增加了对XML的支持,并能和Midas配合实现基于三层的Web开发,李维的Delphi5.x系列书中的电子商务篇对此有着很详细的论述。
从Delphi6开始,Borland提供了新的WebSnap开发框架,这套框架是在InternetExpress基础上重新设计,特点是
1. 增加了更多的控件,提供了Session的支持
2. 提供了Web App
Debugger,可以方便的调试Web程序而无须反复重新启动Web
Server了。
3. 提供了多页模块开发方式,使得我们可以将大型的Web程序分割成小的模块进行开发。
4. 支持Server端脚本,可以在Html页面中嵌入JScript,
VBScript等脚本。
但是在WebSnap中仍然没有解决的最大问题就是开发方式仍然沿袭了InternetExpress的页面设计方式,而不是大家熟悉的拖放控件的开发方式,另外Borland在WebSnap中使用了大量的设计模式,组件之间的关系比较复杂,虽然可以开发功能更强大的Web程序,但是也导致了学习曲线的增高。
到了Delphi7,Borland这回引进了atozedsoftware公司的2002年度Delphi最佳Web开发框架Intraweb,Intraweb同以往所有的Web开发框架相比,是一个革命性的产品,特点就是:
1. 完全支持所见即所得的开发方式,同标准的Windows程序开发几乎一模一样,你只要在窗体上放置编辑框,按钮,组合框,标签等等可视化控件,运行程序后所获得网页和你所设计时所看到的效果是完全一致的。
2. 另外IntraWeb同ASP.net的WebForm的开发方式非常类似,也支持各类Server端事件,象Button的OnClick事件等等。只要学过标准Windows开发的程序员学习IntraWeb开发可以毫不夸张地说,没有任何学习曲线,你甚至可以不需要有任何的HTML、XML、JavaScript等等网站编程知识,你只需要懂得Pascal就足够了。
3. 因为Intraweb的作者Kuduz是著名的网络开发组件Indy的创始人,有着极强的网络服务器开发经验,在Intraweb中集成了一个小巧的http
server,可以方便的进行web程序跟踪调试排错,从我使用的经验来看,比Borland的Web App Debugger运行速度要快,要更稳定。
4. 支持非常简单直观的Session支持,后面我们会讲到。
基于Web的人力资源系统
前面说了IntraWeb那么多的好处,那么我们现在就来看看如何实现一个简单的人力资源系统。下面是我们要做的人力资源系统的用例图:
从用例图我们可以看到这个人力资源系统,主要的功能就是能够增加、删除和修改公司的信息以及公司内的人员的相关信息,并能够对职员的各项信息进行查询。对于IT公司来说,他们最关心的是职员的专业技能,因此要求对职员的各项技能有一个评估,下面就是根据需求做的数据库设计的ER图:
从图中可以看到,本系统的信息模型非常简单,公司同职员的关系是一对多,技能水平同职员的关系是多对一的,剩下的表还有就是管理员表和一些编码表了。要注意的是这个信息模型设计的实际上是有一些问题的,但是我们的目标主要放在如何使用Intraweb开发web程序,这里就不讨论设计的问题了。由ER图生成Access的数据库表,数据库文件名为HR.mdb。
登陆界面的实现
几乎所有的应用系统的第一个界面都是登陆界面,我们首先就来实现一个登陆界面。点击Delphi7的菜单项File |
New…调出New Items Dialog, 执行Intraweb | Stand
Alone Application,IntraWeb会创建两个文件一个是ServerController,一个是窗体文件,名为IWUnit1,创建后的项目编译后会生成的目标文件为EXE类型的Web Server程序,我们这里选择Stand
Alone模式的程序是因为在这种模式下Intraweb可以很方便的进行单步跟踪调试,到发布时我们只需要简单修改一下项目文件就可以将Stand Alone模式的webserver改成ISAPI形式发布的Web
Server。
先保存项目,设定项目名为HR,将IWUnit1改为CFormLogin.pas,因为我们的项目中大量用到了数据库的操作,所以要新建一个数据模块,名为DatamoduleUnit.pas。接下来在CFormLogin界面上放置二个TIWEdit控件,命名为iweUser和iwePass用于输入登陆用户名和口令。再放置两个TIWButton按钮,命名为iwbConfirm和iwbClear,最后放上两个TIWLable标签控件,设定Caption为输入提示信息,完成界面示意图如下:
当用户输入用户名和密码后,点击确定按钮后,我们需要判断用户名和口令的合法性,如果不合法,要提示错误信息,要求用户重新输入,注意要在IE中显示弹出式窗口,需要调用Intraweb的WebApplication全局对象的ShowMessage函数,如果合法则显示主界面。
下面就为确定按钮添加单击事件处理函数,下面是TIWButton的OnClick事件的实现代码:
procedure TformLogin.iwbConfirmClick(Sender:
TObject);
begin
if (trim(iweUser.Text)='') or
(trim(iwePass.Text)='') then
begin
WebApplication.ShowMessage('必须输入用户名和密码');
Exit;
end;
//dmHR是定义在数据模块中联结HR.mdb的Ado联结
with dmHR do
begin
badoOperator.Active:=True;
if not badoOperator.Locate('OperName;Password',
VarArrayOf([trim(iweUser.Text),
trim(iwePass.Text)]), []) then
begin
WebApplication.ShowMessage('无效的用户名或口令,请重新输入');
Exit;
end;
//如果合法,显示主界面
Move(Tformmain);
end;
end;
注意这里我们调用Move函数来切换界面的显示,先释放当前界面,然后再创建新的界面并显示,Move函数的实现如下:
procedure Move(AFormClass:
TIWAppFormClass);
begin
// 释放当前窗体
TIWAppForm(RWebApplication.ActiveForm).Release;
// 创建下一个窗体
AFormClass.Create(RWebApplication).Show;
end;
可以从上面的流程看到,不需要一点的html知识,我们现在就完成了一个登陆界面。
界面继承和框架的支持
编写完登陆界面,我们就要开始编写主界面了。常见的网络程序一般都会有一个比较一致的界面风格,通常是一个功能导航条置于页首或者页面的左侧,同时还会有一个Title页面,显示公司的Logo和联系信息等等。由于一个项目中可能会有很多个这样的页面,如果对于每个页面我们都去做添加这些导航条或者Logo信息的话,首先是这些都是重复的无聊工作,其次如果有一天Logo信息改变了,而类似的页面有100个话,维护工作就是非常巨大了,那么这一问题如何解决呢?
在一般Delphi程序开发中,我们都知道这种问题可以通过框架和界面继承来实现,幸运的是Intraweb同样支持界面继承和框架。
执行File
| New… | Intraweb| Application Form新建一个页面窗体,起名为TformBase, 在界面上放一个TIWImage,设定图像。然后新建一个TFrame,命名为FrameMenu,
在Frame上放四个TIWLink控件,设置Caption分别为”单位列表”,“人员列表”,“人员信息查询”,“权限设置”,当用户点击各个Link的时候,需要显示不同的相关界面,TIWLink的OnClick事件实现如下:
procedure TFrameMenu.iwlCorpClick(Sender:
TObject);
begin
Move(TformCorpList);//显示公司编辑界面
end;
procedure TFrameMenu.iwlHumanClick(Sender:
TObject);
begin
Move(TformHumanList);//显示人员编辑界面
end;
procedure TFrameMenu.iwlQueryClick(Sender:
TObject);
begin
Move(TformQuery);//显示查询界面
end;
procedure TFrameMenu.iwlRightClick(Sender:
TObject);
begin
Move(TformRight);//显示权限界面
end;
完成的TformBase示意图如下:
接下来,我们首先要创建公司信息编辑界面,这个界面要从前面建立的formBase界面继承而来,示意图:
分页控制
由于公司可能会有很多条,这里使用TIWDBGrid来显示公司列表,使用TIWDBNavigator来对公司进行编辑。由于公司数目可能会比较多,所以采用多页显示,要有上一页和下一页的功能,完成的界面示意图如下:
注意上面的核心控件TIWDBGrid要有一些特殊的设定,下面是它的重要的属性设定列表:
object iwgCorp: TIWDBGrid
UseFrame = True //Grid将显示在一个框架中,但Grid行数超出显示区域范围的时候
将,显示滚动条和框架。
FromStart=false
//分页显示时,必须设定FromStart为False
DataSource =
dmHR.dsCorp
Options = [dgIndicator,
dgShowTitles] //显示标题,以及在当前活动记录前显示*号
RefreshMode =
rmAutomatic //每次刷新页面时,会根据数据集更新界面显示
RowLimit = 3
//用于控制当Grid分页显示,每页最多显示的记录数目
end
要想实现分页控制,除了需要设定DBGrid的属性外,我们还需要调用数据集的MoveBy方法来实现滚动,代码如下:
procedure TformCorpList.iwbLastPageClick(Sender:
TObject);
begin
inherited;
//向前翻一页,滚动的数目正好是DBGrid的RowLimit
dmHR.badoCorp.MoveBy(-iwgCorp.RowLimit);
end;
procedure TformCorpList.iwbNextPageClick(Sender:
TObject);
begin
inherited;
//向后翻一页
dmHR.badoCorp.MoveBy(iwgCorp.RowLimit);
end;
列控制
平时我们在使用TDBGrid的时候,选中某一条记录的时候会将数据集的游标定位到相应的记录上,同样的我们也想在TIWDBGrid上实现这一功能,这需要设定IWDBGrid的Column对象的LinkField属性。
比如在图中我们给ChnName字段设定了LinkField为CorpID键值字段,LinkField可以将绑定的字段的当前值传给OnClick事件,可以用来定位数据集的游标,OnClick事件的实现代码如下:
procedure
TformCorpList.iwgCorpColumns1Click(ASender: TObject;
const AValue: String);
var
Id:Integer;
begin
inherited;
//获得CorpID值,使用Locate定位值
Id:=StrToInt(AValue);
dmHR.badoCorp.Locate('CorpId',Id,[]);
end;
运行后,从前面的图中我们可以看到在清华同方公司的下面有一个下滑线,点击超连接就会调用OnClick事件,将数据集游标更新为被点记录,同时更新界面下边的公司中文信息和英文信息。
信息编辑
完成记录浏览功能,我们需要实现对公司信息编辑的功能。其中增加和修改公司记录用一个界面来完成,为了识别操作性质的不同,我们为编辑界面增加了一个EditMode属性,它是枚举类型的,当修改时设为emEdit, 当添加时设为emInsert。下面是代码实现:
//修改公司信息
procedure TformCorpList.iwnCorpEdit(Sender:
TObject);
begin
//inherited;//注意这里不要调用TIWDBNavigator默认的方法
FEditForm.EditMode:=emEdit;
FEditForm.CorpId:=dmhr.badoCorp.FieldByName('CorpId').AsInteger;//传递键值进去
// 显示编辑窗体
FEditForm.Show;
end;
//添加公司信息
procedure TformCorpList.iwnCorpInsert(Sender:
TObject);
begin
//inherited;
FEditForm.EditMode:=emInsert;
// 显示编辑窗体
FEditForm.Show;
end;
对于删除的实现需要一些特殊处理,这里我们不能使用TIWDBNavigator默认的Delete按钮来实现,因为Intraweb默认是先执行删除操作,然后才调用OnClick事件,也就是说我们无法在删除前判断是否允许删除,因为公司下面可能会有很多的员工信息,当公司下员工数目不为零时,我们应该禁止删除公司,所以这里要用Button来实现(注意:在intraweb
5.0.35版本中有这个问题,新的版本是否解决了我就不清楚了)。下面是删除的实现代码:
procedure TformCorpList.iwbDeleteClick(Sender:
TObject);
begin
inherited;
//因为IWDBNavigator默认情况下是先删除,然后才调用我们的事件,因此需要修改这一
//处理过程,所以只好是用Button来实现
with dmHR do
begin
badoQuery.Active:=False;
badoQuery.CommandText:=format('Select count(*) as HumanCount from
tblhuman where CorpID=%s',
[badoCorp.FieldByName('CorpID').AsString]);
badoQuery.Active:=True;
if
badoQuery.FieldByName('HumanCount').AsInteger>0 then
begin
WebApplication.ShowMessage('先删除该公司下所有的人员信息后才能删除该公司');
Exit;
end;
badoCorp.Delete;
end;
end;
最后就是公司信息编辑界面的实现了,下面是编辑界面的示意图:
如果当前编辑操作是添加的话,需要设定界面上的信息为空,如果为编辑则要获得公司信息,填充界面编辑框,下面是实现代码:
const
sCorpIdNotFound = '无法找到相应的公司信息!';
procedure TformEditCorp.SetCorpId(const Value:
Integer);
begin
FCorpId := Value;
//更新界面上的编辑框
if not dmHR.badoCorp.Locate('CorpId',
Value, []) then
raise
Exception.Create(sCorpIdNotFound);
with dmHR.badoCorp do
begin
iweChnName.Text :=
FieldByName('ChnName').AsString;
iweEngName.Text :=
FieldByName('EngName').AsString;
iwePhone.Text :=
FieldByName('Phone').AsString;
iweFax.Text :=
FieldByName('Fax').AsString;
iwmChnInfo.Lines.Text :=
FieldByName('ChnInfo').AsString;
iwmEngInfo.Lines.Text :=
FieldByName('EngInfo').AsString;
iwmComments.Lines.Text :=
FieldByName('Comments').AsString;
end;
end;
procedure TformEditCorp.SetEditMode(const Value:
TEditMode);
begin
FEditMode := Value;
case FEditMode of
emEdit: iwlTitle.Caption :=
'编辑公司信息';
emInsert:
begin
iwbEdit.Caption := '增加公司';
iweChnName.Text := '';
iweEngName.Text := '';
iwePhone.Text := '';
iweFax.Text := '';
iwmChnInfo.Lines.Text := '';
iwmEngInfo.Lines.Text := '';
iwmComments.Lines.Text := '';
end;
end;
end;
procedure TformEditCorp.iwbEditClick(Sender:
TObject);
var
S:String;
begin
case EditMode of
emEdit:
with dmHR
do
begin
adocHR.BeginTrans;
try
//这里直接用Sql来实现的
adocEdit.CommandText :=
format('Update TblCorp Set ChnName=''%s'',
EngName=''%s'', Fax=''%s'',
Phone=''%s'', ChnInfo=''%s'', EngInfo=''%s'',
Comments=''%s'' Where
CorpID=%d', [iweChnName.text, iweEngName.text,
iweFax.Text, iwePhone.Text,
iwmChninfo.Lines.Text, iwmEngInfo.Lines.Text,
iwmComments.Lines.Text,
CorpId]);
adocEdit.Execute;
adocHR.CommitTrans;
badoCorp.Refresh;
except
adocHR.RollbackTrans;
end;
end;
emInsert:
begin
//todo:判断其他字段的值
if
Trim(iweChnName.Text) = '' then
begin
WebApplication.ShowMessage('中文名称不能为空');
Exit;
end;
with
dmHR do
begin
adocHR.BeginTrans;
try
//这里直接用Sql来实现的
adocEdit.CommandText
:=
format('Insert Into TblCorp
(ChnName,EngName,Phone, Fax, ChnInfo, EngInfo,
Comments) Values(''%s'', ''%s'', ''%s'', ''%s'',
''%s'', ''%s'', ''%s'')',
[iweChnName.text, iweEngName.text,
iwePhone.Text, iweFax.Text,
iwmChninfo.Lines.Text,
iwmEngInfo.Lines.Text,
iwmComments.Lines.Text, CorpId]);
S:=adocEdit.CommandText;
adocEdit.Execute;
adocHR.CommitTrans;
badoCorp.Requery;
except
adocHR.RollbackTrans;
end;
end;
end;
end;
hide;
end;
procedure TformEditCorp.iwbCancelClick(Sender:
TObject);
begin
hide;
end;
这里唯一需要注意的就是编辑信息入库后,要想返回主界面,只要调用当前界面的Hide方法就可以了。
人员信息编辑
完成了公司信息界面后,只剩下了职员信息编辑了,界面如下:
里面的浏览等功能的实现方法同公司信息编辑非常类似,这里就不进行详细论述了,其中同公司信息编辑稍微有些不同的是,人员信息编辑界面支持简历上传的操作,上传操作是通过TIWFile来实现的,我们添加一个上传简历的按钮来实现上传的功能,代码如下:
procedure TformEditHuman.iwbUploadClick(Sender:
TObject);
var
FileName: string;
begin
FileName := gsAppPath + 'resumes\' +
iwfResume.Filename;
if FileExists(FileName) then
begin
WebApplication.ShowMessage(format('同名的%s文件已经存在,请将文档改名后重新
上传',
[FileName]));
Exit;
end;
//将简历文档保存起来
iwfResume.SaveToFile(gsAppPath +
'resumes\' + iwfResume.FileName);
iwlResume.Caption :=
ExtractFilename(FileName);
WebApplication.ShowMessage('文件上传成功');
end;
有上传文件,自然就要允许人力资源部门的人员通过Web查看简历,当用户点击resume.doc的超级联结的时候,通过调用WebApplication.SendFile可以将文件内容传送给浏览器,打开Word文档,代码如下:
procedure TformEditHuman.iwlResumeClick(Sender:
TObject);
var
FileName: string;
begin
if (Trim(iwlResume.Caption) <>
'无')
and (Trim(iwlResume.Caption) <> '') then
begin
//gsAppPath定义在SWSystem中
FileName := gsAppPath +
'resumes\' + iwlResume.Caption;
if not FileExists(FileName)
then
begin
WebApplication.ShowMessage('没有找到相应的简历!');
Exit;
end;
WebApplication.SendFile(gsAppPath + 'resumes\' + iwlResume.Caption,
'', '',
true);
end;
end;
职员技能信息同职员是多对一的关系,这里我们采用IWGrid来实现技能列表信息编辑的功能,它其实就是一个TStringGrid的Web版本,用法也非常相似,代码如下:
procedure TformEditHuman.iwbAddClick(Sender:
TObject);
var
I: Integer;
begin
//添加信息,判断是否已经添加过相应技术了,如果是就退出
for I := 0 to iwgTech.RowCount - 1
do
begin
if iwgTech.Cell[I, 0].Text =
iwcTech.Text then
begin
WebApplication.ShowMessage('列表中已经有相应的技术了');
Exit;
end;
end;
with iwgTech do
begin
RowCount := RowCount +
1;
Cell[RowCount - 1, 0].Text :=
iwctech.Text;
Cell[RowCount - 1, 1].Text :=
iwcTechlevel.Text;
Cell[RowCount - 1,
2].Clickable := True;
Cell[RowCount - 1, 2].Text :=
'删除';
end;
end;
注意在添加技能的同时,我们还在界面中的技能网格中添加了删除超级连接,设定删除连接所在的单元Clickable属性为true,则当用户点击删除超级连接的时候,会调用IWGrid的CellClick事件,在事件中我们可以删除记录所在行,代码如下:
procedure TformEditHuman.iwgTechCellClick(const
ARow, AColumn: Integer);
var
HumanTech: THumanTech;
begin
//删除当前Row的记录
iwgTech.DeleteRow(ARow);
end;
调试运行
编写完代码后,点击Run,会显示下面的可执行界面,这其实就是一个迷你的http server,选中菜单命令Run|Execute,才会显示我们WebServer的界面。
发布部署
前面我们提到了,调试时尽量选择Stand Alone 模式,发布时选用ISAPI方式发布,那么如何将Stand
Alone的程序改成ISAPI的程序呢?
非常简单,只要将我们hr.dpr中program 改成Library,将IWInitStandAlone改成 IWInitISAPI就可以了。
program HR;
{PUBDIST}
uses
IWInitStandAlone,
ServerController in
'ServerController.pas' {IWServerController: TDataModule},
CHR in 'CHR.pas' {formMain:
TIWForm1},
DatamoduleUnit in 'DatamoduleUnit.pas'
{dmHR: TDataModule},
CFrameMenu in 'CFrameMenu.pas'
{FrameMenu: TFrame},
CFormBase in 'CFormBase.pas' {formBase:
TIWAppForm},
CFormCorpList in 'CFormCorpList.pas'
{formCorpList: TIWAppForm},
CFormHumanList in 'CFormHumanList.pas'
{formHumanList: TIWAppForm},
CFormQuery in 'CFormQuery.pas'
{formQuery: TIWAppForm},
CFormRight in 'CFormRight.pas'
{formRight: TIWAppForm},
CFormEditCorp in 'CFormEditCorp.pas'
{formEditCorp: TIWAppForm},
CWebUtils in 'CWebUtils.pas',
CFormEditHuman in 'CFormEditHuman.pas'
{formEditHuman: TIWAppForm},
CFormLogin in 'CFormLogin.pas'
{formLogin: TIWAppForm};
{$R *.res}
begin
IWRun(TformLogin,
TIWServerController);
end.
总结
Intraweb的功能非常强大,这里由于文章篇幅有限,我只是简单的介绍了Intraweb的主要功能,其他象Session控制、Flash、动态图表、ActiveX、样式表、模板等功能,感兴趣的朋友可以参看intraweb的例子。