DELPHI基础教程

第六章 文件管理(二)

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;

  [目录] [上一页] [下一页]