用Delphi写MIS类程序(二)

1.3 第二个程序,来点数据保存的代码

在上一节中我们已完成了一个十分简单的MDI程序,但这个程序除了能打开和关闭外就什么也干不了,现在我们想象一下实际的情况,一个MIS的程序很多时候都需要保存用户录入的数据到数据库中去,而且为了保证这些数据能不被遗留的保存,程序一般会提示用户。现在我们在第二个程序中加入这些代码,当然为了程序的简单,我们暂时还是不讨论连接数据库的情况,我们用一个外置的文本文件(dataa.txt)代替数据库。

FormA上添加一个memo和一个button控件,并添加以下的代码

……

private

    FMemoChange : boolean;

    FDataFile : string;

    function LoadData : boolean;

    function SaveData : boolean;

    function NeedSave : boolean;

    { Private declarations }

  public

    { Public declarations }

  end;

 

var

  frmChildA: TfrmChildA;

 

implementation

 

{$R *.dfm}

 

procedure TfrmChildA.btnSaveClick(Sender: TObject);

begin

  SaveData;

end;

 

procedure TfrmChildA.FormClose(Sender: TObject; var Action: TCloseAction);

var

  kk: Integer;

begin

  Action := caFree;

  if NeedSave then

    begin

      kk := MessageBox(WindowHandle, PChar(Caption + '需要保存吗?'),

                    PChar( 'MDI Tutorial'),

                    MB_YESNOCANCEL + MB_ICONQUESTION + MB_DEFBUTTON1);

      if  kk = IDCancel then

        Action := caNone

      else if kk = IDYes then

        begin

          if not self.SaveData then

            begin

                  Action := caNone;

            end

        end;

    end;

end;

 

procedure TfrmChildA.FormCreate(Sender: TObject);

begin

  FDataFile := ExtractFilePath(Application.ExeName)+ '/dataa.txt';

  self.LoadData;

  FMemoChange := false;

end;

 

procedure TfrmChildA.FormDestroy(Sender: TObject);

begin

  frmChildA := nil;

end;

 

function TfrmChildA.LoadData: boolean;

begin

  result := true;

  memo1.Lines.LoadFromFile(FDataFile);

end;

 

procedure TfrmChildA.Memo1Change(Sender: TObject);

begin

  FMemoChange := true;

end;

 

function TfrmChildA.NeedSave: boolean;

begin

  result := FMemoChange ;

end;

 

function TfrmChildA.SaveData: boolean;

begin

  result := true;

  memo1.Lines.SaveToFile(FDataFile);

  FMemoChange := false;

end;

 

MainForm上添加如下的代码

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);

begin

  if frmChildA <> nil then

    begin

      frmChildA.Close;

      if frmChildA <> nil then Action := caNone;

    end;

end;

红色的为新加的代码,先说明一下这些代码的作用:

当用户创建FormA时,程序会读取目录下的DataA.txt文件,并将其内容加载到memo1中,用户可修改memo1中的内容,修改后可按save按钮保存。另外,如果用户修改了内容但忘记保存就退出的话,程序会弹出一个是否保存的提示。程序的代码比较简单,也十分易懂。如果是要保存在真正的数据库中的话,那么只要替换SaveData, LoadDataNeedSave的内容即可。在FormB中我们也可以增加类似的代码来完成相应的功能。

现在我们从重新来考虑一下整个程序的结构,我们如果新加入一个Form的话,要为每一个Form准备一个变量,及一段退出保存的询问代码,而在主窗体中要添加创建该窗体及为每一个创建的子窗体判断是否需要保存的代码。如果整个程序只是两三个子窗体的话,那么按上面的写法并无不可,但如果窗体太多的话(一般的MIS起码都有十到二十个不同的子窗体)。那么整个程序类同的代码就会十分多,但这些类同的代码却不能简单的通过一、两个公共函数来解决问题,那么我们应该怎样办好呢?

 

1.4 创建子窗体的父窗体

Delphi是一种面向对象的语言,像其它语言一样支持类的继承,窗体也是一个类,因此也支持这种机制,只是可能平时我们用得不多而已。

我们IDE中创建一个新的窗体,将其保存为ChildFormDefine.pas,并将该窗体命名为CustomChildForm,同时与FormA相似,我们要设置这个Form的一系列属性,如字体,FormStylefsMDIChildWindowState值为wsMaximized等等MDI子窗应有的属性值。同时要将MDI_Tutorial中的Application.CreateForm(TCustomChildForm, CustomChildForm);删除。下面是ChildFormDefine.pas的代码:

……

type

  TCustomChildForm = class(TForm)

    procedure FormClose(Sender: TObject; var Action: TCloseAction);

  public

    function LoadData : boolean; virtual;

    function SaveData : boolean; virtual;

    function NeedSave : boolean; virtual;

  end;

 

var

  CustomChildForm: TCustomChildForm;

 

implementation

uses

  MainForm;

 

{$R *.dfm}

 

procedure TCustomChildForm.FormClose(Sender: TObject; var Action: TCloseAction);

var

  kk: Integer;

begin

  Action := caFree;

  if NeedSave then

    begin

      kk := MessageBox(WindowHandle, PChar(Caption + '需要保存吗?'),

                    PChar( 'MDI Tutorial'),

                    MB_YESNOCANCEL + MB_ICONQUESTION + MB_DEFBUTTON1);

      if  kk = IDCancel then

        Action := caNone

      else if kk = IDYes then

        begin

          if not self.SaveData then

            begin

                  Action := caNone;

            end

        end;

    end;

  if Action = caFree then

    frmMain.RemoveMDIChild(self);

end;

 

function TCustomChildForm.LoadData: boolean;

begin

  result := true;

end;

 

function TCustomChildForm.NeedSave: boolean;

begin

  result := true;

end;

 

function TCustomChildForm.SaveData: boolean;

begin

  result := true;

end;

……

这个CustomChildForm中的代码与FormA的代码差不多,但因为这个是父类窗体,所以它不会保存任何的数据,所有与数据相关的函数都定义成virtual,以方便它的子类能override它们。另外,我们在FormClose的响应事件中多加了一个语句

if Action = caFree then

    frmMain.RemoveMDIChild(self);

这句的作用是将窗体从窗体列表中删除。

以下列出MainForm.pas的代码:

unit MainForm;

interface

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, Menus, ChildFormDefine;

type

  TMDIChildListItem = class

    aForm : TCustomChildForm;

    aKey : TObject;

  end;

  TfrmMain = class(TForm)

    MainMenu1: TMainMenu;

    MenuTest1: TMenuItem;

    mnFormA: TMenuItem;

    mnFormD: TMenuItem;

    mnFormC: TMenuItem;

    procedure FormCreate(Sender: TObject);

    procedure FormClose(Sender: TObject; var Action: TCloseAction);

    procedure mnFormCClick(Sender: TObject);

    procedure mnFormDClick(Sender: TObject);

    procedure FormDestroy(Sender: TObject);

  private

    FMDIChildList : TList;

    function GetMDIChildItems(index : integer) : TMDIChildListItem;

    procedure AddMDIChildList (aKey : TObject; aForm : TCustomChildForm);

    function SearchMDIChild(aKey : TObject) : TCustomChildForm;

    { Private declarations }

  public

    procedure RemoveMDIChild(aForm : TCustomChildForm);

    { Public declarations }

  end;

 

var

  frmMain: TfrmMain;

 

implementation

uses

  ChildFormC, ChildFormD;

 

{$R *.dfm}

 

procedure TfrmMain.mnFormCClick(Sender: TObject);

var

  frm : TCustomChildForm;              //注二

begin

  frm := SearchMDIChild(Sender);

  if  frm = nil then

    begin

      frm := TfrmChildC.Create(Application);

      AddMDIChildList(Sender, frm);

    end;

  frm.Show;

end;

 

procedure TfrmMain.mnFormDClick(Sender: TObject);

var

  frm : TCustomChildForm;

begin

  frm := SearchMDIChild(Sender);

  if  frm = nil then

    begin

      frm := TfrmChildD.Create(Application);

      AddMDIChildList(Sender, frm);

    end;

  frm.Show;

end;

 

procedure TfrmMain.RemoveMDIChild(aForm: TCustomChildForm);

var

  i : integer;

  afi :  TMDIChildListItem;

begin

  for i := 0 to self.FMDIChildList.Count - 1 do

    begin

      afi := GetMDIChildItems(i);

      if aForm = afi.aForm then

        begin

          self.FMDIChildList.Remove(afi);

          afi.Free;

          break;

        end;

    end;

end;

 

function TfrmMain.SearchMDIChild(aKey: TObject): TCustomChildForm;

var

  i : integer;

  afi :  TMDIChildListItem;

begin

  result := nil;

  for i := 0 to self.FMDIChildList.Count - 1 do

    begin

      afi := GetMDIChildItems(i);

      if aKey = afi.aKey then

        begin

          result := afi.aForm;

          break;

        end;

    end;

end;

 

procedure TfrmMain.AddMDIChildList(aKey : TObject; aForm : TCustomChildForm);

var

  afi : TMDIChildListItem;

begin

  afi := TMDIChildListItem.Create;

  afi.aKey := aKey;

  afi.aForm := aForm;

  self.FMDIChildList.Add(afi);

end;

 

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);

var

  i : integer;

begin

  while FMDIChildList.Count > 0 do   //1

  begin

    i := FMDIChildList.Count;

    GetMDIChildItems(0).aForm.Close;

    if i = FMDIChildList.Count then

      begin

        Action := caNone;

        exit;

      end;

  end;

end;

 

procedure TfrmMain.FormCreate(Sender: TObject);

begin

  FMDIChildList := TList.Create;

end;

 

procedure TfrmMain.FormDestroy(Sender: TObject);

var

  i : integer;

begin

  for i := 0 to FMDIChildList.Count - 1 do

    GetMDIChildItems(i).Free;

  FMDIChildList.Free;

end;

 

function TfrmMain.GetMDIChildItems(index: integer): TMDIChildListItem;

begin

  result := FMDIChildList.Items[index];

end;

 

end.

 

这个MainForm.pas与原来的版本大不相同,最主要增加的是一个子窗体列表管理的功能。工作原理是这样的:当用户点击菜单时,系统会主动调用mnFormXClick(Sender: TObject)这个函数,其中Sender就是这个菜单项的地址(每一个菜单项的地址都是唯一的),我们利用这个地址作为键值,通过SearchMDIChild(aKey: TObject)函数去查找一张子窗体列表,如果在列表中有这个项目,那么意味着这个窗体已经被创建过,我们只需要调用Show方法即可。如果找不到相应的键值,那么就创建一个窗体再Show出来,并将该窗体和对应的菜单地址值加入到列表中去,以便下次查找。

当子窗体被用户关闭的时候,子窗体会先根据NeedSave的情况去判断是否需要保存,如需要保存,那么就会调用SaveData方法,接着在窗体被Free掉之前,调用主窗体的方法frmMain.RemoveMDIChild(self);将自己从窗体列表中删除。

如果用户直接关闭的是主窗体的话,那么会通过(注一)中的代码,通过一个循环,每次把列表中的第一个窗体取出来,并试图关闭,如果能关闭成功,那么就会继续往下取;如果某子窗体不能关闭,那么就会停下来。

现在,我们有了主体窗体和子窗体的父窗体,那么让我们再创建两个真正的子窗体。我们通过File->New->Other->Inheritable Items->CustomChildForm,就可以创建出窗体了,创建的这两个新窗体,我们也要像上一节讲的那样要在MDI_Tutorial.dpr中去除自动创建窗体的代码,同时在主窗体中新增相应的菜单项,并在每一个菜单响应函数中加入(注二)中的代码。

子窗体中就不用加任何控制代码了,当然,为了保证显示的效果,我们可以在子窗体中加入一个TMemo,然后像上节中的例子一样,加上读取和保存的代码:

unit ChildFormC;

interface

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, ChildFormDefine, StdCtrls;

type

  TfrmChildC = class(TCustomChildForm)

    Memo1: TMemo;

    btnSave: TButton;

    procedure FormCreate(Sender: TObject);

    procedure btnSaveClick(Sender: TObject);

    procedure Memo1Change(Sender: TObject);

  private

    FMemoChange : boolean;

    FDataFile : string;

  public

    function LoadData : boolean; override;

    function SaveData : boolean; override;

    function NeedSave : boolean; override;

  end;

var

  frmChildC: TfrmChildC;

implementation

{$R *.dfm}

 

procedure TfrmChildC.btnSaveClick(Sender: TObject);

begin

  inherited;

  SaveData;

end;

 

procedure TfrmChildC.FormCreate(Sender: TObject);

begin

  inherited;

  FDataFile := ExtractFilePath(Application.ExeName)+ '/dataa.txt';

  self.LoadData;

  FMemoChange := false;

end;

 

function TfrmChildC.LoadData: boolean;

begin

  result := true;

  memo1.Lines.LoadFromFile(FDataFile);

end;

 

procedure TfrmChildC.Memo1Change(Sender: TObject);

begin

  inherited;

  FMemoChange := true;

end;

 

function TfrmChildC.NeedSave: boolean;

begin

  result := FMemoChange ;

end;

 

function TfrmChildC.SaveData: boolean;

begin

  result := true;

  memo1.Lines.SaveToFile(FDataFile);

  FMemoChange := false;

end;

end.

 

至此,我们就完成了第二个程序了,在这个程序中,我们简化了子窗体的中的一些公共控制代码,保证子窗体中的代码能尽量保持纯洁(没有与业务逻辑无关的其它控制代码)。但我们也为了这个“纯洁性”付出了不少的代价,主窗体中的增加的代码十分多,而且每调用任一个子窗体都要加一大段代码,这段代码还能简化吗?我们将在下一节去解决。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值