後一頁
前一頁
回目錄
回首頁
第六章 文件管理(一)

         文件是同一型式元素的有序集合,是記憶體與外設間傳輸數據的渠道。一些外設如顯示器、鍵盤、列印機等都可以看作文件,但最常用的還是磁片檔,這也是本章我們主要討論的物件。

         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。前者大部分以文件變數為操作物件,而後者大部分以檔案標簽或文件句柄為操作物件。這�堿陘F方便讀者的使用,我們按功能重新進行了分類。在下一節中主要應用I/O Routine主題下的過程,而在第四節的綜合舉例中主要應用File_Management Routine主題下的過程。

       另外,Windows提供了許多有關文件管理的API函數。雖然在一般情況下,利用Delphi提供的函數已足夠解決問題,但有時候仍然需要使用Windows API。在(6.4.4.2)中我們就用到了Windows API函數GetDriveType。有關Windows API函數的情況,請讀者參閱相關的資料,這�堣ㄕA進行介紹。

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;  


後一頁
前一頁
回目錄
回首頁