DELPHI基础教程
第六章 文件管理(一) 文件是同一类型元素的有序集合,是内存与外设间传输数据的渠道。一些外设如显示器、键盘、打印机等都可以看作文件,但最常用的还是磁盘文件,这也是本章我们主要讨论的对象。 Delphi 继承了 Object Pascal 的文件管理功能,并有很大的发展,其中最主要的是提供了用于文件管理的标准控件,同时也提供了更多的文件管理函数。利用 Delphi 的强大功能,开发一个自己的文件管理系统就成为很容易的事。 本章首先介绍 Delphi 文件管理的基本概念和标准过程 / 函数,并提供了一个记录文件的应用实例,这是从我们实际课题开发中提取出来的。而后介绍 Delphi 提供的文件控件的使用方法。最后提供的一个综合例程 MDI 文件管理器则是对 Delphi 文件管理功能的综合应用。 6.1 文件类型和标准过程 Delphi 同 Object Pascal 一样支持三种文件类型,即:文本文件、记录文件、无类型文件。 6.1.1 文本文件 文本文件类型的变量用如下方法声明: var TextFileVar: Text ; 文本文件是以行为单位进行读、写操作的。由于每一行长度不一定相同,不能计算出给定行在文件中的确切位置,因而只能顺序地读写。而且文本文件只能单独为读或写而打开,在一个打开的文本文件上同时进行读、写操作是不允许的。 6.1.1.1 文本文件的打开、关闭 文本文件的打开需要两个步骤: (1). 文件变量与文件名关联; (2). 初始化读写。 联文件变量与文件名调用 AssignFile 标准过程: AssignFile ( TextFileVar , FileName ) ; FileName 既可以是全路径名,也可以仅是文件名。对于后者系统将在当前目录下查找。 AssignFile 是 Delphi 新提供的一个函数,其功能等价于 Object Pascal 中的 Assign 。而 Assign 在 Delphi 中更多地被用作一个方法名。 初始化读写有三种方式: 1. Reset : 为读打开文件并把文件指针移动到文件首; 2. Rewrite : 为写创建一个新文件; 3. Append : 为写打开存在的文件并把文件指针定位在文件尾。 当使用 Reset 或 Append 过程而文件不存在时将会引发一个 I/O 异常。有关 I/O 异常的处理请参看本章例程和第十二章中的介绍。 文件的关闭很简单,只须调用 CloseFile 过程即可。 虽然 Delphi 应用程序在退出时会自动关闭所有打开的文件,但自己动手关闭文件可以确保释放文件句柄,并使程序的可移植性增强。 为保持兼容, Delphi 也允许用户用 Assign 建立关联, Close 关闭文件。 6.1.1.2 文本文件的读写 从文本文件中读取信息用 Read 、 Readln 两个标准过程。 当读入数值时, Read 、 Readln 假定数值是用一个或多个空格分开的,而不是逗号、分号或其它字符。对如下一条语句: Read ( TextFileVar , Num1 , Num2 , Num3 ) ; 如果文件中的数值是: 100 200 300 则能够成功读入,而若文件中的数值是 100 200 , 300 则 Read 读入“ 200 ,”并试图把它转化成一个数值时会引发一个异常。 当读入字符是字符串时, Read 、 Readln 过程总是读取尽可能多的字符填充到字符串变量中或一直读到行结束符为止。因此从文本文件中读取格式化的字符串数据,必须声明与其长度相匹配的字符串变量。如果要从文件中读取单词,必须先把文件中的每一行读入字符串,然后再从字符串中逐个分析出单词。或者一次只从文本文件中读入一个字符并测试每个字符后是否是单词断开处。 格式化字符串之间的分隔符应读入到一个临时变量中,而字符串与数值、数值与数值间的分隔符读入时会自动识别剔除。对如下一行数据: Mon 12:10 40 50 定义 var Day: string[3] ; Time: string[5] ; Num1, Num2: Integer ; 则须用如下的 read 语句读入: read ( TextFileVar , Day , c , Time , Num1 , Num2 ) ; C 为一个临时字符变量。 6.1.1.3 文本文件的编辑 在 Delphi 中实现对一个文本文件的编辑,只须让其与一个 Tmemo 控件建立关联即可: Memo1.Lines.LoadFromFile ( TextFileName ) ; 这样在 TMemo 上所做的一切修改当调用 Memo 部件的 SaveToFile 方法后都会反映到文件中去。 6.1.2 记录文件 记录文件是一种操作更为灵活的文件类型。它允许同时为读和写打开,而且由于记录文件中每条记录的长度固定,所以可随机存取。 记录文件的类型变量可如下声明: var RecordFileVar: file of RecordType; RecordType 是一个自定义的记录类型。 有关记录文件的操作我们将在下一节中结合例程进行讨论。 6.1.3 无类型文件 无类型文件提供了底层的 I/O 通道,可用于存取可变长度记录的文件。经常用于文件的复制操作中。由于 Delphi 提供了更好的方法 ( 见第四节 ) ,所以无类型文件很少使用。有兴趣的读者可参看 BlockRead 、 BlockWrite 两个联机帮助主题。 6.1.4 Delphi 的文件管理标准过程 根据功能我们把标准过程划分为十一类进行介绍。 6.1.4.1 文件的打开与关闭 AssignFile : 把一个外部文件名和一个文件变量相关联 Reset :打开一个存在的文件 Rewrite :创建并打开一个新文件(或覆盖原有文件) Append : 以添加方式打开一个文件(只适用于文本文件) CloseFile : 关闭一个打开的文件 FileOpen :打开一个特定的文件并返回文件句柄 FileCreate :创建一个给定文件名的文件并返回文件句柄 FileClose : 关闭一个特定句柄的文件 后边三个文件主要供系统内部使用,在文件复制的编程中也往往会用到。它们操作的对象是文件句柄而不是文件变量。 6.1.4.2 文件定位 Seek : 把文件当前位置移到指定部分 FilePos : 返回文件的当前位置 Eoln : 返回行结束标志 EOF : 返回文件结束标志 FileSeek : 改变当前文件指针的位置 Seek 与 FileSeek 的区别是: 1. Seek 仅用于记录文件; 2. FileSeek 的参数是文件句柄、偏移量、起始位置。其中起始位置有文件首、当前位置、文件尾三种选择。 Seek 的参数是文件变量、偏移量,偏移量是从文件首开始定位的。 3. FileSeek 的偏移量以字节数来计算,而 Seek 是根据记录号进行移动。 Seek 、 FilePos 仅用于记录文件。但任何文件都可以看作是基于字节的记录文件。下面一段程序表示了它们的用法。 { 该例子的设计界面为一个包含 TOpenDialog 部件的窗体。} uses Dialogs; var f: file of Byte; size: Longint; S: String; y: Integer; begin if OpenDialog1.Execute then begin AssignFile(f, OpenDialog1.FileName); Reset(f); size := FileSize(f); S := 'File size in bytes: ' + IntToStr(size); y := 10; Canvas.TextOut(5, y, S); y := y + Canvas.TextHeight(S) + 5; S := 'Seeking halfway into file...'; Canvas.TextOut(5, y, S); y := y + Canvas.TextHeight(S) + 5; Seek(f,size div 2); S := 'Position is now ' + IntToStr(FilePos(f)); Canvas.TextOut(5, y, S); CloseFile(f); end; end. 6.1.4.3 文件删除与截断 Erase : 删除一个存在的文件 DeleteFile : 删除一个文件 Truncate : 从文件当前位置将文件截断 Erase 与 DeleteFile 的区别是: Erase 以文件变量为参数,当文件不能删除时引起一个异常; DeleteFile 以文件名为参数,当文件不存在或不能删除时返回 False ,而并不引起一个异常。 6.1.4.4 文件名操作 Rename :文件更名,以文件变量为操作对象 RenameFile :文件更名,参数为文件的原名和新名 ChangeFileExt :改变文件扩展名 ExpandFileName :返回文件全路径名 ExtractFileExt :返回文件扩展名 ExtractFileName :从全路径名中返回文件名 ExtractFilePath :返回特定文件的路径 6.1.4.5 文件属性 FileGetAttr :返回文件属性 FileSetAttr :设置文件属性 6.1.4.6 文件状态 FileSize :返回文件对象大小 IOResult :返回上一次 I/O 操作的状态 FileExists :检测文件是否存在 6.1.4.7 文件日期 DateTimeToFileDate :把 Delphi 日期格式转换为 DOS 日期格式 FileDateToDateTime :把 DOS 日期格式转换为 Delphi 日期格式 FileGetDate :返回文件的 DOS 日期时间戳 FileSetDate :设置文件的 DOS 日期时间戳 6.1.4.8 文件读写 Read , Readln :从文本或记录文件中读取变量 Write :将指定变量写入文本或记录文件 Writeln :将指定变量写入文本文件并写入一个行结束标志 FileRead :从一个指定文件中读取变量 FileWrite :向指定文件写入数据 FileRead 和 FileWrite 都是以文件句柄为操作对象,主要供系统内部使用。 6.1.4.9 目录操作 MkDir :创建当前目录的子目录 ChDir :改变当前目录 GetDir :返回特定磁盘的当前目录 RmDir :删除一个空子目录 6.1.4.10 磁盘操作 DiskFree :返回磁盘自由空间 DiskSize :返回特定磁盘的大小 6.1.4.11 文件查找 FileSearch :查找目录中是否存在某一特定文件 FindFirst :在目录中查找与给定文件名(可以包含匹配符)及属性集相匹配 的第一个文件 FindNext :返回符合条件的下一个文件 FindClose :中止一个 FindFirst / FindNext 序列 有关文件管理标准过程 / 函数的更详细资料,请查阅 Delphi 相关的 Help 主题。以上的大部分过程在后面都有应用实例,读者可以从中体会其用法。 在 Delphi 的联机帮助 Help 系统中把有关文件的过程 / 函数分为两个主题: I/O Routine 和 File_Management Routine 。前者大部分以文件变量为操作对象,而后者大部分以文件名或文件句柄为操作对象。这里为了方便读者的使用,我们按功能重新进行了分类。在下一节中主要应用 I/O Routine 主题下的过程,而在第四节的综合举例中主要应用 File_Management Routine 主题下的过程。 另外, Windows 提供了许多有关文件管理的 API 函数。虽然在一般情况下,利用 Delphi 提供的函数已足够解决问题,但有时候仍然需要使用 Windows API 。在 (6.4.4.2) 中我们就用到了 Windows API 函数 GetDriveType 。有关 Windows API 函数的情况,请读者参阅相关的资料,这里不再进行介绍。 6.2 记录文件的应用 6.2.1 任务介绍 在这一节,我们开发一个系统安全性综合评估方法管理系统。系统安全性在复杂项目开发中十分重要,但由于牵涉面广因而很难获得客观、全面的评估值。鉴于此我们提出多角度、多侧面评估而后定量集成的思路,并在此基础上提出了多种安全性综合评估方法。每种方法由不同部门进行评估而后把结果汇总、综合。 为此我们定义如下的记录类型: type TNature = (Micro,Macro); { 方法性质,分为微观和宏观两类} TMethod = Record Name: string[20]; { 方法名} Condition: string[40]; { 方法适用条件} Nature: TNature; { 方法性质} Result: Real; { 方法评估值} end; 用来记录不同方法的信息。 由于不同方法的条件、性质不同,因而对工程开发的不同阶段适用方法集也不同。因此需要根据实际情况对方法集进行管理。我们把每一方法作为一条记录,每一方法集作为一个记录文件。下面讨论系统的实现方法。 6.2.2 设计基本思路 本系统要实现的基本功能是文件的打开、创建、关闭、显示,记录的增加、修改、删除以及结果的综合和显示。为此我们使用了两组按钮分别用于文件和记录的操作, 使用一个 StringGrid 控件来显示文件内容,使用一个只读编辑框显示结果的综合。 其中各部件的名称、功能如下表所示: 表 6.1 主窗口部件的设计 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 部件名称 主要属性 备注 ────────────────────────────────────── RecFileForm BorderStyle=bsDialog 文件打开后把文件名附到窗口标题后 Position=poScreenCenter StringGrid1 大小行数动态确定 HazAttr( 编辑框 ) ReadOnly=True 显示综合结果 OpenButton TabOrder=0 打开一个记录文件 , 若文件不存在则创建 NewButton Caption=' 打开 ' 创建一个记录文件 , 若文件存在则打开 CloseButton Caption=' 关闭 ' 关闭一个已打开的文件 AddButton Caption=' 增加 ' 增加一条记录 ModifyButton Caption=' 修改 ' 修改一条记录 DeleteButton Caption=' 删除 ' 删除一条记录 CalcuButton Caption=' 计算 ' 计算最终结果并显示 ExitButton Caption=' 退出 ' 系统终止。若当前有打开的文件则先关闭 OpenDialog1 Filter= 选择或输入欲打开的文件 'Record File(*.Rec)|.Rec |Any File(*.*)|*.*' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 另外, StringGrid1 、 HazAttr 的标题用两个标签框 (Label) 来显示。 另外我们还需要一个编辑对话框。其中四个编辑框 Name 、 Condition 、 Nature 、 Result 分别对应 TMethod 记录的四个域。 为协调程序运行,我们定义了一组全局变量。各变量的类型、作用如下表。 表 6.2 全局变量及其作用 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 变量名 类型 作用 ───────────────────────────────── MethodFile MethodFileType 与当前打开文件相关联的文件变量 FileName string[70] 当前打开文件的文件名 Count Count 当前打开文件的记录总数 CurrentRec Integer 当前处理记录号 FileOpened Boolean 当前是否有文件打开 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 记录文件类型 MethodFileType 的定义为 type MethodFileType = file of TMethod; 布尔变量 FileOpened 用于控制文件按钮的使能、变灰,记录按钮的反应以及系统结束时是否需要首先关闭文件。 6.2.3 记录文件的打开和创建 记录文件的打开和创建同文本文件一样也需要关联和初始化两个步骤。同文本文件唯一的不同是不能使用 Append 过程。 记录文件缺省情况下以读写方式打开,如果想以只读或只写方式打开,则需要修改 System 单元中定义的变量 FileMode 的值。 FileMode 的取值和意义如下表。 表 6.3 FileMode 的取值和意义 ━━━━━━━━━━━━━━ 取值 意义 ────────────── 0 只读 1 只写 2 读写 ━━━━━━━━━━━━━━ FileMode 是一个全局变量,对它的每次修改都将影响所有 Reset 的操作,因此在打开自己的文件后应还原它的值。 在本系统中,当用户按下“打开”按钮时,首先弹出一个标准文件打开对话框,要求用户输入或选择文件名。确认后如果该文件名的文件存在,则用 Reset 打开,若不存在则创建。程序清单如下。 procedure TRecFileForm.OpenButtonClick(Sender: TObject); begin if OpenDialog1.Execute then FileName := OpenDialog1.FileName else exit; AssignFile(MethodFile,Filename); try Reset(MethodFile); FileOpened := True; except On EInOutError do begin try if FileExists(FileName) = False then begin ReWrite(MethodFile); FileOpened := True; end else begin FileOpened := False; MessageDlg(' 文件不能打开 ',mtWarning,[mbOK],0); end; except On EInOutError do begin FileOpened := False; MessageDlg(' 文件不能创建 ',mtWarning,[mbOK],0); end; end; end; end; if FileOpened = False then exit; Count := FileSize(MethodFile); if Count>0 then ChangeGrid; RecFileForm.Caption := FormCaption+' -- '+FileName; NewButton.Enabled := False; OpenButton.Enabled := False; CloseButton.Enabled := True; end; 首先系统试图用 Reset 打开一个文件,并置 FileOpened 为 True 。如果文件不能打开,则引发一个 I/O 异常。在异常处理过程中,首先检测文件是否存在。若不存在则创建这个文件。否则是其它原因引发的异常,则把 FileOpend 重置为 False , 并显示信息“文件不能打开”。在文件创建过程中仍可能引发异常,因而在一个嵌套的异常处理中把 FileOpened 重置为 False ,并提示信息“文件不能创建”。 有关异常处理的内容请读者参看第十二章。这段程序说明:异常处理机制不仅能使我们的程序更健壮,而且为编程提供了灵活性。 当用户按下“创建”按钮时,系统首先弹出一个标准输入框,要求用户输入文件名,确认后系统首先检测文件是否存在。若存在则直接打开,否则创建一个新文件。打开或创建过程导致异常,则重置 FileName 和 FileOpened 两个全局变量。 procedure TRecFileForm.NewButtonClick(Sender: TObject); begin FileName := InputBox(' 输入框 ',' 请输入文件名 ',''); if FileName = '' then Exit; try AssignFile(MethodFile,FileName); if FileExists(FileName) then begin Reset(MethodFile); Count := FileSize(MethodFile); if Count>0 then ChangeGrid; end else begin Rewrite(MethodFile); count := 0; end; FileOpened := true; Except on EInOutError do begin FileName := ''; FileOpened := False; end; end; if FileOpened then begin NewButton.Enabled := False; OpenButton.Enabled := False; CloseButton.Enabled := True; RecFileForm.Caption := FormCaption+' -- '+FileName; end; end; 当文件打开或创建后,所要做的工作有: ● 若文件非空,则计算文件长度,并用文件内容填充 StringGrid1 ● “创建”、“打开”按钮变灰,“关闭”按钮使能 ● 把文件名附到窗口标题后 6.2.4 记录文件的读入和显示 定义一个全局变量 Count 用来保存文件中的记录个数。当文件装入时: Count := FileSize(MethodFile) ; 如果 Count > 0 ,则首先确定 StringGrid1 的高度、行数。为保证 StringGrid1 不会覆盖窗口下面的编辑框,定义一个常量 MaxShow 。当 Count < MaxShow 时,记录可全部显示;当 Count >= MaxShow 时, StringGrid1 自动添加一个滚动棒。为保证滚动棒不覆盖掉显示内容, StringGrid1 的宽度应留有余地。 确定 StringGrid1 高度、行数的代码如下: With StringGrid do if count < MaxShow then Height := DefaultRowHeight * (Count+1)+10 else Height := DefaultRowHeight * MaxShow+10; RowCount := Count+1; end; 而后从文件中逐个读入记录并显示在 StringGrid1 的相应位置: for i := 1 to Count do begin Read(MethodFile,MethodRec); ShowMethod(MethodRec,i); end; ShowMehtod 是一个过程,用来把一条记录填入 StringGrid1 的一行中。对于 Name 、 Condition 域而言,只须直接赋值即可;而对 Nature 域需要把枚举类型值转化为对应意义的字符串 (0 :“微观”, 1 :“宏观” ) ;而对 Result 域则需要把数值转化为一定格式的字符串: Str (MethodRec.Result:6:4,ResultStr) ; StringGrid1.Cells[3,Pos] := ResultStr; 即 Result 显示域宽为 6 ,其中小数点后位数为 4 。 6.2.5 增加一条记录 当用户单击“增加”按钮时屏幕将会弹出一个记录编辑模式对话框 EditForm 。在编辑框中填入合适的内容并按 OK 键关闭后,相应值写入一个 TMethod 类型的变量 MethodRec 中。其中 Nature 和 Result 域需要进行转换。之后增加的记录添加到 StringGrid1 的显示中。 最后文件定位于尾部,写入当前记录,总记录数加 1 。 Seek(MethodFile,Count); Write(MethodFile,MethodRec); Count := Count+1; 完整的程序清单如下: procedure TRecFileForm.AddButtonClick(Sender: TObject); var MethodRec: TMethod; Rl: Real; k: Integer; EditForm: TEditForm; begin if FileOpenEd = False then Exit; EditForm := TEditForm.Create(self); if EditForm.ShowModal <> idCancel then begin HazAttr.text := ''; MethodRec.Name := EditForm.MethodName.text; MethodRec.Condition := EditForm.Condition.text; case EditForm.NatureCombo.ItemIndex of 0: MethodRec.Nature := Micro; 1: MethodRec.Nature := Macro ; end; Val(EditForm.Result.text,Rl,k); MethodRec.Result := Rl; with StringGrid1 do begin if Count < MaxShow then Height := Height+DefaultRowHeight; RowCount := RowCount+1; end; ShowMethod(MethodRec,Count+1); seek(MethodFile,Count); write(MethodFile,MethodRec); Count := Count+1; end; end; 6.2.6 修改记录 首先获取当前记录位置: CurrentRec := StringGrid1.Row - 1; 而后打开编辑对话框并显示当前值。修改完毕后,修改结果保存在一个记录中并在 StringGrid1 中重新显示。 最后修改结果写入文件: Seek(MethodFile,CurrentRec); Write(MethodFile,MethodRec); 完整程序如下: procedure TRecFileForm.ModifyButtonClick(Sender: TObject); var MethodRec: TMethod; Rl: Real; k: Integer; EditForm: TEditForm; begin if FileOpened = False then Exit; EditForm := TEditForm.Create(self); CurrentRec := StringGrid1.Row-1; with EditForm do begin MethodName.text := StringGrid1.Cells[0,CurrentRec+1]; Condition.text := StringGrid1.Cells[1,CurrentRec+1]; if StringGrid1.Cells[2,CurrentRec+1] = ' 微 观 ' then NatureCombo.ItemIndex := 0 else NatureCombo.ItemIndex := 1; Result.text := StringGrid1.Cells[3,CurrentRec+1]; if ShowModal <> idCancel then begin HazAttr.text := ''; MethodRec.Name := MethodName.text; MethodRec.Condition := Condition.text; case NatureCombo.ItemIndex of 0: MethodRec.Nature := Micro; 1: MethodRec.Nature := Macro ; end; Val(Result.text,Rl,k); MethodRec.Result := Rl; ShowMethod(MethodRec,CurrentRec+1); seek(MethodFile,CurrentRec); write(MethodFile,MethodRec); end; end; end; |