後一頁 前一頁 回目錄 回首頁 |
6.2.7 記錄的解除、插入、排序 解除一條記錄的基本思路是:獲取目前記錄的位置並把該位置後的記錄逐個向前移動。 文件在最後一條記錄前截斷。 for i:=CurrentRec+1 to Count-1 do begin seek(MethodFile,i); read(MethodFile,MethodRec); seek(MethodFile,i-1); Write(MethodFile,MethodRec); end; Truncate(MethodFile); 為避免誤解除,在進行解除操作前彈出一個訊息框進行確認。解除後要更新全局變數的值和顯示內容: Count := Count - 1; ChangeGrid; 完整的程式如下: procedure TRecFileForm.DeleteButtonClick(Sender: TObject); var NewFile: MethodFileType; MethodRec: TMethod; NewFileName: String; i: Integer; begin if FileOpened = False then Exit; CurrentRec := StringGrid1.Row-1; if CurrentRec < 0 then Exit; if MessageDlg('Delete Current Record ?', mtConfirmation, [mbYes, mbNo], 0) = idYes then begin HazAttr.text := ''; for I := CurrentRec+1 to Count-1 do begin seek(MethodFile,i); read(MethodFile,MethodRec); seek(MethodFile,i-1); Write(MethodFile,MethodRec); end; Truncate(MethodFile); Count := Count-1; ChangeGrid; end; end; 這裏所顯示的解除操作簡單明了。但在程式開始設計時我卻走了一條彎路,後來發現雖然這種方法用於記錄的解除操作顯得笨拙、可笑,但卻恰恰是記錄插入、排序的思想。 這種思想的核心是建立一個新文件存檔更新後的內容。若新文件順利建立,則解除原文件,否則恢復原來的文件。程式清單如下: procedure TRecFileForm.DeleteButtonClick(Sender: TObject); var NewFile: MethodFileType; MethodRec: TMethod; NewFileName: String; i: Integer; begin if FileOpened = False then Exit; CurrentRec := StringGrid1.Row-1; if CurrentRec < 0 then Exit; if MessageDlg('Delete Current Record ?', mtConfirmation, [mbYes, mbNo], 0) = idYes then begin HazAttr.text := ''; NewFileName := ChangeFileExt(FileName,'.sav'); try AssignFile(NewFile,FileName); ReWrite(NewFile); Except On EInOutError do begin Rename(MethodFile,FileName); Exit; end; end; for i := 1 to Count do if I <> CurrentRec+1 then begin MethodRec := GridToRec(i); Write(NewFile,MethodRec); end; closeFile(MethodFile); try AssignFile(MethodFile,Filename); Reset(MethodFile); except on EInOutError do begin DeleteFile(FileName); AssignFile(MethodFile,NewFileName); Reset(MethodFile); Rename(MethodFile,FileName); Exit; end; DeleteFile(NewFileName); Count:=Count-1; ChangeGrid; end; end; 對於記錄插入,方法基本同上。對於排序,可先將關鍵欄位讀入排序,而後再按排序結果對應的記錄號順序重寫文件。 6.2.8 結果綜合 對不同方法的評估結果,可按一定的公式進行綜合。當用戶按下“計算”按鈕時,系統進行計算並把綜合結果寫入HazAttr唯讀編輯框中。 為保證結果顯示的正確性,每次增加、修改、解除操作確認後HazAttr編輯框清空。 6.2.9 編輯對話方塊的輸入檢查 當用戶按一下“增加”或“修改”按鈕時系統將彈出一個編輯對話方塊,讓用戶輸入或修改記錄內容。其中的三個編輯框,一個群群組合列示方塊分別對應TMethod 的四個欄位。由於TMethod的Result欄位必須是[0,1]間的小數,因此當用戶按OK鍵關閉對話方塊時應進行型式和範圍檢查。 在VB中我做過同樣的工作,那時需要對用戶輸入的鍵碼逐個進行判斷。但這種方法很繁瑣、很難做圓滿(如不能很好地支援編輯鍵)。而Object Pascal提供了更好的方法。這種方法的關鍵就在於它的型式轉換函數Val: procedure Val(Str: String;var V; var Code: Integer); V是由Str轉換成的整型或實型數。若字元串非法,則出錯位置返至Code;否則置Code為0。字元串非法並不會引發一個轉換異常。 如果轉換後的數超出了我們的範圍,則顯式把Code置為-1。最後統一通過偵測Code是否為0來判斷輸入是否合法。 我們把輸入檢查放在對話方塊的OnCloseQuery事件處理過程中。如輸入非法,則禁止對話方塊關閉,並將輸入焦點置於Result編輯框中。但假如用戶按了Cancel按鈕,則這種檢查是多餘的。為此定義一個布爾變數IsCancel,對話方塊產生時置為False。假如用戶按下Cancel,則置為True,此時OnCloseQuery事件不進行輸入檢查。 對話方塊的OnCloseQuery事件處理過程的程式清單如下: procedure TEditForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); var Res: Real; k: Integer; begin if IsCancel = False then begin val(Result.text,Res,k); if (Res > 1) or (Res < 0) then k := -1; if k <> 0 then begin MessageDlg('非法輸入 !',mtWarning,[mbOK],0); Result.text := ''; CanClose := False; Result.SetFocus; end; end; end; 6.2.10 文件和系統的關閉 文件關閉須呼叫CloseFile過程: CloseFile(MethodFile); 並對系統的狀態重新進行設定。 系統關閉時首先偵測目前是否有打開的文件。若有則先關閉文件。這在主視窗的OnCloseQuery事件中實現。 實現文件關閉的程式清單如下: procedure TRecFileForm.CloseButtonClick(Sender: TObject); begin if FileOpened then begin CloseFile(MethodFile); FileOpened := False; ClearGrid; OpenButton.Enabled := True; NewButton.Enabled := True; CloseButton.Enabled := False; RecFileForm.Caption := FormCaption; end; end; 實現系統關閉前檢查的程式清單如下: procedure TRecFileForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if FileOpened then closeFile(MethodFile); end; 6.2.11 記錄文件小結 我們所舉的例子雖然簡單,但基本覆蓋了記錄文件操作的主要方面。這裏關鍵問題在於靈活應用Delphi提供的文件管理函數。同時,為了保證程式的健壯性應對異常進行捕獲並處理。在資料庫應用技術發展的今天,記錄文件的重要性也許有所下降,但物件我們這裏所處理的簡單問題它仍有用武之地。 這裏所舉的例子一次只能處理一個文件。但讀者可以很容易把它改為一個MDI程式。雖然對於這裏的實際情況來說,似乎並無必要。 6.3 文件控件的應用 Delphi文件管理的最大特色是提供了一群群組文件操作控件。利用這些控件我們可以快速開發一個檔案標簽瀏覽系統。其功能強大與其所需書寫代碼之少所形成的強烈反差,正是Dephi生命力的體現。 6.3.1 文件控件及其相互關係 Delphi提供的專用文件控件如下表所示。 表6.4 Delphi專用文件控件───────────────────────────────────── 控件名 功 能 ————————————————————————————————————— DriveComboBox 磁碟機群群組合列示方塊。用於選擇目前磁碟機 FileListBox 文件列示方塊。用於顯示目前工作目錄中的文件和選中目前文件 FilterComboBox 文件型式群群組合列示方塊。用於選擇顯示文件的型式 DirectoryOutline 目錄樹(6.4節專門介紹) ───────────────────────────────────── 以上控件前四個在Component Palette(部件選擇板)的System頁中,DirectoryOutline在Component Palette的Samples頁中。 以上文件控件再加上文件編輯框、目錄標籤框(事實上是一般的編輯框、標籤框)就可以構成一個完整的文件作業系統。它們之間的聯繫幾乎不用代碼支援,只要設定好相應的屬性就可以了。 FileEdit、DirLabel、FileListBox、FileFilterComloList、 DirectoryListBox、DriveComboList六個控件間的屬性聯繫如下: DriveComboList .DirList := DirectoryListBox; DirectoryListBox.DirLabel := DirLabel; DirectoryListBox.FileList := FileListBox; FileFilterComboList.FileList := FileListBox; FileListBox.FileEdit := FileEdit; 以上聯繫可以在設計時完成。只要打開相應屬性的選擇列示方塊進行選擇即可。也可以在執行時利用如上的賦值語句建立聯繫。 文件控件的關鍵屬性基本上都在以上聯繫中反映出來了。除此之外,FileFilterComboList有一個Filter屬性,用來設定群群組合列示方塊的選擇項;FileListBox 有一個Mask屬性,用於設定顯示文件的型式,這就允許FileListBox在脫離FileFilterComboList單獨應用時仍能根據需要顯示特定的文件。在6.4節中我們將應用這一功能。 文件控件的方法、事件基本是從ListBox和ComboBox中繼承的。但FileListBox 中有一個ApplyFilePath方法很有用,我們將在後邊給出其用法。 6.3.2 檔案標簽瀏覽找到系統的設計思路 作為文件控件的應用實例,我們開發了一個簡單的檔案標簽瀏覽找到系統。這個系統可用於檔案標簽的顯示,把選中的文件寫入列示方塊,並能按文件編輯框中輸入的萬用字元符號對文件進行找到。 表6.5 部件的設計 ───────────────────────────────────── 部件 屬性 功能 ————————————————————————————————————— FileCtrForm Position=poDefault 主視窗 DirLabel 顯示目前工作目錄 FileEdit TabOrder=0 顯示目前文件/輸入文件顯示匹配符 FileListBox1 FileEdit=FileEdit 顯示目前工作目錄文件 DirectoryListBox1 DirLabel=DirLabel 顯示目前磁碟機目錄 FileList= FileListBox1 DriveComboBox1 DirList= DirectoryListBox1 選擇目前磁碟機 FilterComboBox1 FileList=FileListBox1 選擇文件顯示型式 Filter='All Files(*.*)|*.*| Source Files(*.pas)|*.pas| Form Files(*.dfm)|*.dfm| Project Files(*.dpr)|*.dpr' ListBox1 顯示選中或找到的文件 Button1 Caption='找到' 按 FileEdit 中的內容進行找到 Button2 Caption='結束' 結束系統 ───────────────────────────────────── 6.3.3 檔案標簽瀏覽找到系統的功能和實現 6.3.3.1 按指定後綴名顯示目前工作目錄中的文件 實現這一功能只需要在控件間建立正確的聯繫即可,不需要代碼支援。建立聯繫的方法如(6.3.1)中的介紹。 6.3.3.2 把選中的文件加入到列示方塊中 在FileListBox1的OnClick事件中: procedure TFileCtrForm.FileListBox1Click(Sender: TObject); begin if Searched then begin Searched := False; ListBox1.Items.Clear; Label5.Caption := 'Selected Files'; end; if NotInList(ExtractFileName(FileListBox1.FileName),ListBox1.Items) then ListBox1.Items.Add(ExtractFileName(FileListBox1.FileName)); end; Searched是一個全局變數,用於標明ListBox1目前顯示內容是找到的結果還是從FileListBox1中已選的文件。 函數NotInList用於判斷待加入的字元串是否已存在於一個TStrings物件中。函數返回一個布爾型變數。 NotInList的具體實現如下。 Function TFileCtrForm.NotInList(FileName: String;Items: TStrings): Boolean; var i: Integer; begin for I := 0 to Items.Count-1 do if Items[i] = FileName then begin NotInList := False; Exit; end; NotInList := True; end; 6.3.3.3 按指定匹配字元串顯示目前工作目錄中的文件 當在FileEdit中輸入一個匹配字元串,並回車,文件列示方塊將顯示匹配結果。這一功能在FileEdit的OnKeyPress事件中實現。 procedure TFileCtrForm.FileEditKeyPress(Sender: TObject; var Key: Char); begin if Key = #13 then begin FileListBox1.ApplyFilePath(FileEdit.Text); Key := #0; end; end; 文件列示方塊提供的ApplyFilePath方法是解決這一問題的關鍵所在。 6.3.3.4 按指定匹配字元串找到目前工作目錄中的文件 為了進行比較,我們用另一種方法來實現文件的找到功能,即利用標準過程FindFirst、FindNext。FileList1與ListBox1 中的內容完全一致。 當用戶按一下“找到”按鈕時,與FileEdit 中字元串相匹配的文件將顯示在ListBox1中。下面是實現代碼。 procedure TFileCtrForm.Button1Click(Sender: TObject); var i: Integer; SearchRec: TSearchRec; begin Searched := True; Label5.Caption := 'Search Result'; ListBox1.Items.Clear; FindFirst(FileEdit.text,faAnyFile,SearchRec); ListBox1.Items.Add(SearchRec.Name); Repeat i := FindNext(SearchRec); If i = 0 then ListBox1.Items.Add(SearchRec.Name); until i <> 0; end; SearchRec是一個TSearchRec型式的記錄。TSearchRec的定義如下: TSearchRec = record Fill: array[1..21] of Byte; Attr: Byte; Time: Longint; Size: Longint; Name: string[12]; end; 在這一結構中提供了很多資訊,靈活應用將給程式設計帶來很大方便。下面我們舉幾個例子。 1. 偵測給定文件的大小。 function GetFileSize(const FileName: String): LongInt; var SearchRec: TSearchRec; begin if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then Result := SearchRec.Size else Result := -1; end; 這一程式將在下一節中應用。 2. 獲取給定文件的時間戳,事實上等價於FileAge函數。 function GetFileTime(const FileName: String): Longint; var SearchRec: TSearchRec; begin if FindFirst(ExpandFileName(FileName),faAnyFile, SearchRec) = 0 then Result := SearchRec.Time else Result := -1; end; 3. 偵測文件的屬性。如果文件具有某種屬性,則 SearchRec.Attr And GivenAttr > 0 屬性常數對應的值與意義如下表: 表6.6 屬性常數對應的值與意義 ──────────────────── 常數 值 描述 ————————————————————— faReadOnly $01 唯讀文件 faHidden $02 隱藏文件 faSysFile $04 系統文件 faVolumeID $08 卷標文件 faDirectory $10 目錄文件 faArchive $20 檔案文件 faAnyFile $3F 任何文件 ──────────────────── 6.4 文件管理綜合舉例:文件管理器的實現 在本章的最後,我們利用Delphi提供的文件控件和文件管理函數開發一個簡單的文件管理器。雖然這一文件管理器還無法和Windows提供的文件管理器相比擬,但它也為一般的文件操作提供了足夠多的功能,而且如果讀者感興趣,還可以對它做進一步的擴充。在後邊的拖放操作一章中,我們就為它提供了拖放支援,使它看起來更象一個“文件管理器”。 6.4.1 設計基本思路 6.4.1.1 視窗設計 文件管理器的主視窗是一個多文檔介面(MDI)。有關文件、目錄的顯示和文件管理功能的實現都放在子視窗中。在程式執行過程中將根據需要彈出一些完成不同操作的對話方塊。這些對話方塊都是在需要時動態產生的。表6.7給出了本程式所設計窗體的清單。 表6.7 FileManger窗體清單 ────────────────────────────────────── 窗體類 功能 用於建立該類窗體的選擇表項 —————————————————————————————————————— TFileManager 主視窗 TFMForm 子視窗 Windows|New Window TFileAttrForm 顯示文件屬性 File|Properties;Function|Search TChangeForm 文件移動、拷貝、改名、改變 File|Move.Cope.Rename 目前工作目錄等操作的輸入對話方塊 Directory|change Directory TSearchForm 輸入待找到文件的標簽和路徑 Function|Search TDiskViewForm 顯示磁碟資訊 Function|Disk View TViewDir 輸入待建立的子目錄 Directory|CreateDirectory TAboutBox 顯示版權資訊 Help|About ────────────────────────────────────── 6.4.1.2 介面設計 主視窗介面主要是主選擇表和用於表示目前工作目錄、目前文件的狀態條。 表6.8 主視窗介面設計 ───────────────────────────── 部件 屬性 功能 ————————————————————————————— FileManager Style=fsMDI 主視窗 WindowMenu=Windows Position=poDefault MainMenu1 主選擇表 FilePanel Align=alBottom 顯示目前選中文件 BevelInner=bvLowered BevelWidth=2 DirectoryPanel Align=alBottom 顯示目前選中目錄 Alignment=taLeftJustify BevelInner=bvLowered BevelWidth=2 ──────────────────────────────
主視窗主選擇表包括File、WIndows、Help三項。File選擇表項在子視窗產生時被子視窗同名選擇表項所取代。設定Windows、Help的GroupIndex = 9,可以使子視窗產生時這兩個選擇表項仍存在。 子視窗界麵包括主選擇表、目錄樹(DirectoryOutline)、文件列示方塊、 用於顯示磁碟機的標籤集(TabSet)以及三個用於顯示磁碟機型式的TImage部件。 表6.9 子視窗介面設計 ─────────────────────────────────────── 部件 屬性 功能 ——————————————————————————————————————— FMForm ActiveControl=DirectoryOutline 子視窗 Position=poDefault Style=fsMDIChild MainMenu1 主選擇表 DriveTabSet Align=alTop 顯示磁碟機 style=tsOwnerDraw DirectoryOutline Align=alLeft 顯示目前磁碟機的目錄樹 options=[ooDrawTreeRoot, ooDrawFocusRect,ooStretchBitmaps] FileList Align=alClient 顯示目前工作目錄中的文件 FileType=[ftReadOnly, ftHidden,ftSystem,ftArchive,ftNormal] ShowGlyphs=True Network(Image) Picture(Network.bmp) 標誌網路磁碟機 Vsible=False Floppy(Image) Picture(Floppy.bmp) 標誌軟驅 Visible=False Fixed(Image) Picture(Fixed.bmp) 標誌硬驅 Visible=False ───────────────────────────────────────
子視窗主選擇表包括File、Function、Directory三個選擇表項, 分別用於完成文件的基本管理功能、其它管理功能和目錄管理功能。 由於對話方塊介面設計很簡單,這裏不再進行贅述。 讀者可直接參考後面將給出的對話方塊介面圖(圖6.8---6.13)進行設計。
6.4.2 子視窗的建立、布置和關閉
子視窗的建立、布置由父視窗的Windows選擇表控制,其選擇表項如下: ● New Windows : 建立新的子視窗 ● Tile : 非重疊顯示 ● Cascade : 重疊顯示 ● ArrangeIcon : 安排圖示 ● Minimized All : 極小化所有子視窗
子視窗的建立只需要簡單呼叫窗體的Create方法:
FileMan := TFMForm.Create(Application);
子視窗的標準排列方式直接呼叫MDI視窗的標準方法Tile、Cascade和ArrangeIcons。 極小化所有子視窗的實現利用MDI視窗的兩個屬性:MDIChildCount和MDIChildren:
for i := 0 to MDICount - 1 do MDIChildren[i].Windowstate := wsMinimized;
子視窗關閉時釋放記憶體空間,為此在子視窗TFMForm的OnClose事件中令
Action := OnFree;
為了保持和Windows的File Manager的一致性,我們也禁止關閉最後一個子視窗,這需要在子視窗的OnCloseQuery事件處理過程中實現:
If FileManager.MDIChildCount <= 1 then CanClose := False;
CanClose是OnCloseQuery事件過程返回的一個參數,用於判定視窗是否可以關閉。 由於這一過程歸子視窗所有,因而MDIChildCount前必須加上其物件名FileManager。 但不幸的是:這樣一來我們的程式無法終止了!原來MDI視窗關閉前首先關閉其所有的子視窗。如果子視窗不能關閉,MDI視窗也不能關閉。 為此我們需要判斷發出關閉訊息的是子視窗的系統選擇表還是選擇表的Exit項。 定義一個全局變數
var ExitClick: Boolean;
在子視窗的Exit1Click事件處理過程中:
ExitClick := True; FileManager.Exit1Click(Sender);
子視窗關閉前可以利用這一全局變數偵測是否應關閉:
If (FileManager.MDIChildCount <= 1) and (Not ExitClick) then CanClose := False;
6.4.3 文件控件的聯繫
在本例中我們使用了一群群組新的控件:TabSet、DirectoryOutline、FileListBox,用於顯示和選擇磁碟機、目錄和文件。與(6.3)中所用方法相比,使用這一群群組控件需要少量的代碼支援。 TabSet與DirectoryOutline的聯繫在TabSet的Click事件處理過程中建立:
With DriveTabSet do DirectoryOutline.Drive := Tabs[TabIndex][1];
DirectoryOutline與FileListBox的聯繫在DirectoryOutline的Change事件處理過程中建立:
FileList.Directory := DirectoryOutline.Directory; FileList.Update;
6.4.4 DriveTabSet的自畫風格顯示 Dephi為一些控件提供了自畫風格的顯示,如ListBox、ComboBox、TabSet等。 在缺省情況下,這些控件自動顯示文本。而在自畫風格下,擁有控件的窗體在執行時間內自己畫出控件的每一項目。 自畫風格顯示通常的應用是為項目除文本外再加入圖形顯示。能以自畫風格顯示的控件有一個共同特點:都擁有一個TStrings型式的項目鍊。由於TStrings類的特點(參第三章),它們都可以加入一個和對應文本相聯繫的物件。 而這正是自畫風格顯示的關鍵。 通常情況下產生一個自畫風格需要三個步驟: 1.設定自畫風格; 2.向字元串鍊表加入圖形物件; 3.畫出自畫項目。 6.4.4.1 設定自畫風格 控件屬性Style 用於設定自畫風格。對於DriveTabSet,我們把Style 屬性設定為tsOwnerDraw。 對於ListBox、ComboBox等控件的設定與TabSet略有差異,讀者可參閱連線輔助敘述文檔。 6.4.4.2 向字元串鍊表加入圖形物件 1.在應用程式中加入圖片部件 在本程式中我們設定了三個圖片部件NetWork、Floppy、Fixed,並分別與三個點陣圖文件NetWork.bmp、Floppy.bmp、Fixed.bmp相關聯。 2.把圖片加入到字元串鍊表中 根據字元串鍊表的性質,我們可以把物件與已存在的字元串建立聯繫,也可以同時加入字元串和物件。這裏我們採用後一種方法。 在子視窗的OnCreate事件處理過程中,我們利用一個回圈依次偵測從a到z的磁碟機是否存在以及磁碟機的型式。這利用了Windwos API函數GetDrivetype, 如果磁碟機不存在則返回0,否則返回磁碟機的型式(DRIVE_REMOVABLE、DRIVE_FIXED、DRIVE_REMOTE)。根據磁碟機型式我們可以判斷與文本(磁碟機名)同時加入到Tabs中的不同圖形物件。在加入過程中,DriveTabSet的TabIndex被設定為目前磁碟機。 程式清單如下: procedure TFMForm.FormCreate(Sender: TObject); var Drive, AddedIndex: Integer; DriveLetter: Char; begin for Drive := 0 to 25 do begin DriveLetter := Chr(Drive + ord('a')); case GetDrivetype(Drive) of DRIVE_REMOVABLE: AddedIndex := DriveTabSet.Tabs.AddObject(DriveLetter, Floppy.Picture.Graphic); DRIVE_FIXED: AddedIndex := DriveTabSet.Tabs.AddObject(DriveLetter, Fixed.Picture.Graphic); DRIVE_REMOTE: AddedIndex := DriveTabSet.Tabs.AddObject(DriveLetter, Network.Picture.Graphic); end; if UpCase(DriveLetter) = UpCase(FileList.Drive) then DriveTabSet.TAbIndex := AddedIndex; end; end; 6.4.4.3 畫出自畫項目 當把一個控件的風格設定為自畫時,Windows不再負責往螢幕上畫出控件的項目,而是為每個可見項目產生自畫事件。應用程式可以通過處理自畫事件畫出控件的項目。 1.確定自畫項目的大小 對於TabSet而言,這在OnMeasureTab事件處理過程中完成。我們需要把DriveTabSet每個標籤的寬度增大到足以同時放下文本和點陣圖。 procedure TFMForm.DriveTabSetMeasureTab(Sender: TObject; Index: Integer; var TabWidth: Integer); var BitmapWidth: Integer; begin BitmapWidth := TBitmap(DriveTabSet.Tabs.Objects[Index]).Width; Inc(TabWidth, 2 + BitmapWidth); end; 由於TStrings的Objects屬性中存放的物件都是TObject型式,並沒有Width屬性,因而需要再把它轉化為TBitmap型式的物件: BitmapWidth := TBitmap(DriveTabSet.Tabs.Objects[Index]).Width;
|
後一頁 前一頁 回目錄 回首頁 |