1.程序的基本界面
1.1 使用MDI 还是 SDI
在Windows的程序中,不论窗体的表现形式如何,其实都可以大致分为MDI和SDI两种不同的程序。在很多的教程中,都是用SDI(Single Document Interface)作为例子的,因为SDI比较的简单,程序大都只用一个窗体作界面,几乎所有的功能都集中在这个窗体上。当有些程序确实要用到另外的窗体时,就在主窗体上加个按钮来激活一个模式(model)窗体,但以这种方式表现出来的程序除了编写好像简单一些外,其它方面如外观、功能、扩展等等都十分的不好,所以我建议一般写MIS的程序都使用MDI方式(当然如果应用十分的简单,那用SDI也是好的)。
MDI(Multiple Document Interface)多重文件界面最典型的应用莫过于旧版的MS Office 97中的Word与Excel,它们有一个主的窗体,同时可以在主窗体里面新建多个样式相同或相似的子窗体。我们在Delphi中,通过代码的生成器十分容易就可以得到这种效果的程序,但这是我们真正需要的东西吗?大家不妨可以试一试。(选择 “File->New->Other->Delphi Project->MDI Application)
1.2构造第一个程序
假设现在要编写一个进销存的软件,由于这软件要求使用进仓,出仓,销售,报表等不同的界面,为了使程序中的窗体不至于太松散,所以我们采用MDI方式而不用SDI。我们将建立一个MDI主窗体,然后在它里面添加相应的菜单,同时编写相应的进仓,出仓,销售,报表等的子窗体,我们希望达到的效果是:当用户点菜单上的功能时,相应的子窗体能显示出来供用户使用。现在我们可以模拟一下这个程序。
在Delphi中新建一个VCL Forms Application,将这个程序保存为MDI_Tutorial,然后将默认的Form保存为MainForm.pas,把这个Form的Name改为frmMain,另外设定FormStyle为fsMDIForm。接着新建两个Form,将这两个文件保存为ChildFormA、ChildFormB,将其改名为frmChildA、frmChildB,接着将这两个Form的FormStyle为fsMDIChild,同时为了识别这两个Form,分别在Form上放上Label,分别设定Label的Caption为FormA,FormB。最后,我们在主窗体(frmMain)上放上一个MainMenu的控件,然后在它上面添加上相应的菜单项(mnFormA,mnFormB)。现在虽然一行代码没写,但我们还是可以运行这个程序。运行这个程序后,会发现不但主窗口显示正常,而且连两个子窗口都显示出来,更烦人的是,这两个子窗口居然是不能关掉的,这可不是我们想要的效果。但为什么有这样的效果呢?这个两子窗口是什么时候创建的呢?我们可以打开MDI_Tutorial.dpr,在这个文件中有以下的语句:
1 Application.Initialize;
2 Application.CreateForm(TfrmMain, frmMain);
3 Application.CreateForm(TfrmChildA, frmChildA);
4 Application.CreateForm(TfrmChildB, frmChildB);
5 Application.Run;
在第3,4行程序中,我们可以发现frmChildA和frmChildB的构建语句,原来它们是在整个程序一加载时就创建完成的。所以如果我不想它们这么早就构建的话,那么可以把这两句删除掉。这样这两个窗体就不会被创建了。
现在我们来编写MainForm中菜单的响应事件:
uses
ChildFormA, ChildFormB;
procedure TfrmMain.mnFormBClick(Sender: TObject);
begin
frmChildB := TfrmChildB.Create(Application);
frmChildB.Show;
end;
procedure TfrmMain.mnFormAClick(Sender: TObject);
begin
frmChildA := TfrmChildA.Create(Application);
frmChildA.Show;
end;
编写好后,我们再运行一次看看。我们会发现,现在菜单上的按钮能正确的响应事件了,即当我点mnFormA时会产生FormA,点击mnFormB时会产生FormB。十分的正常,但当我多次点击mnFormA时,会对应产生多个的FormA,这又不是我们想要的效果(但这确实是MDI文档应有的效果,你看看Word就知道了),为什么不是我们想要的效果呢?试想一下,如果FormA是一个“进仓功能”的窗体时,多点击几下就产生了多个进仓录入界面,先不要说程序的代码如何处理数据,就是用户看了也会晕掉,该在那个窗体中录入才好呢?而且这两个子窗体同样是不能正常关闭的。
其实这可能是大多数初学Delphi的人的迷惑?我们应该怎样去解决这个问题呢?
首先我们先解决不能关闭的问题,我们先找到子窗体对应的关闭事件(以FormA为例),添加以下代码:
procedure TfrmChildA.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
这个Action:=caFree是告诉窗体当用户要关闭窗体时要同时释放实例,这样就可以达到真正的关闭窗体了。而为了不让同一个窗体(严格来说是同一个菜单项目所对应的窗体)重复出现,我要增加一些代码去控制
在FormA中(FormB中也是类似的)
procedure TfrmChildB.FormDestroy(Sender: TObject);
begin
frmChildB := nil;
end;
在MainForm中加入:
procedure TfrmMain.mnFormBClick(Sender: TObject);
begin
if frmChildB = nil then
frmChildB := TfrmChildB.Create(Application);
frmChildB.Show;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
frmChildA := nil;
frmChildB := nil;
end;
procedure TfrmMain.mnFormAClick(Sender: TObject);
begin
if frmChildA = nil then
frmChildA := TfrmChildA.Create(Application);
frmChildA.Show;
end;
红色的地方是修改原来代码的。我们用frmChildA变量去记住FormA有没有产生,当frmChildA为nil的时候就产生一个实例,并将该实例赋值给它,然后再显示,如果frmChildA不是空的,那么就意味着它带了个实例,直接Show出来就可以了;当子窗体被Destroy的时候就把frmChildA设定为nil以保证下次可以正常的创建一个窗体。通过这样,我们就可以达到只产生一个子窗体实例的目的了。
在第一个程序结束之前,我们还要再设定一下这些子窗体的表现形式,我希望这些子窗体一旦创建就是最大化的,那只需修改了窗体中的WindowState值为wsMaximized,同样,如果你如果希望你的程序在Windows98下(虽然这个东西很老,但确还是有人在用的,包括我的母亲!)能比较好看一点,那么请将子窗体的字体设定为“宋体”。
我们的第一个符合MIS开发需求的MDI结构的程序模型就这样完成了,但有没改进的空间呢?是否可以消灭每个子窗体里面那些重复的代码,重复的设置呢?在主窗体中的重复代码呢?