![]() |
VC教程 | ||||||||||||||||
|
文件管理综合举例:文件管理器的实现 在本章的最后,我们利用Delphi提供的文件控件和文件管理函数开发一个简单的文件管理器。虽然这一文件管理器还无法和Windows提供的文件管理器相比拟,但它也为一般的文件操作提供了足够多的功能,而且如果读者感兴趣,还可以对它做进一步的扩充。在后边的拖放操作一章中,我们就为它提供了拖放支持,使它看起来更象一个“文件管理器”。 图(6.5)是程序运行后的界面。读者可以发现,它和Windows的程序管理器非常类似。 图6.5 程序管理器运行界面
设计基本思路
窗口设计
文件管理器的主窗口是一个多文档界面(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.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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
主窗口界面如图所示。
图6.6 主窗口界面
主窗口主菜单包括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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 子窗口设计界面如图所示。
图6.7 子窗口界面
子窗口主菜单包括File、Function、Directory三个菜单项, 分别用于完成文件的基本管理功能、其它管理功能和目录管理功能。 由于对话框界面设计很简单,这里不再进行赘述。 读者可直接参考后面将给出的对话框界面图(图6.8---6.13)进行设计。
子窗口的创建、布置和关闭
子窗口的创建、布置由父窗口的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;
文件控件的联系
在本例中我们使用了一组新的控件: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;
DriveTabSet的自画风格显示
Dephi为一些控件提供了自画风格的显示,如ListBox、ComboBox、TabSet等。 在缺省情况下,这些控件自动显示文本。而在自画风格下,拥有控件的窗体在运行时间内自己画出控件的每一项目。 自画风格显示通常的应用是为项目除文本外再添加图形显示。能以自画风格显示的控件有一个共同特点:都拥有一个TStrings类型的项目链。由于TStrings类的特点(参第三章),它们都可以加入一个和对应文本相联系的对象。 而这正是自画风格显示的关键。 通常情况下产生一个自画风格需要三个步骤: 1.设置自画风格; 2.向字符串链表添加图形对象; 3.画出自画项目。
设置自画风格
控件属性Style 用于设置自画风格。对于DriveTabSet,我们把Style 属性设置为tsOwnerDraw。 对于ListBox、ComboBox等控件的设置与TabSet略有差异,读者可参阅联机帮助文档。
向字符串链表添加图形对象
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; 画出自画项目
当把一个控件的风格设置为自画时,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;
2.画出每个自画项目
这在TabSet的OnDrawTab事件处理过程中完成。这一事件处理过程的参数中包含了待画项目索引、画板、待画区域、是否被选中等。这里我们只利用了前三个参数。事实上利用最后一个参数,我们可以对被选中的标签进行一些特殊的视觉效果处理。这一工作就留给读者自己去完成。
procedure TFMForm.DriveTabSetDrawTab(Sender: TObject; TabCanvas: TCanvas; R: TRect; Index: Integer; Selected: Boolean); var Bitmap: TBitmap; begin Bitmap := TBitmap(DriveTabSet.Tabs.Objects[Index]); with TabCanvas do begin Draw(R.Left, R.Top + 4, Bitmap); TextOut(R.Left + 2 + Bitmap.Width, R.Top + 2, DriveTabSet.Tabs[Index]); end; end;
文件管理基本功能的实现
在子窗口的File菜单中,定义了文件管理的基本功能,它们是: ● Open :打开或运行一个文件(从文件列表框双击该文件可实现同样效果) ● Move :文件在不同目录间的移动 ● Copy :文件拷贝 ● Delete :文件删除 ● Rename :文件更名 ● Properties :显示文件属性
文件打开
文件打开功能可以运行一个可执行文件,或把文件在与之相关联的应用程序中打开。文件总是与创建它的应用程序相关联,这种关联可以在Windows的文件管理器中修改。要注意的是:文件的关联是以后缀名为标志的,因而对一个文件关联方式的修改将影响所有相同后缀名的文件。 文件打开功能实现的关键是利用了Windows API函数ShellExecute 。由于Windows API函数的参数要求字符串类型是PChar,而Delphi中一般用的是有结束标志的String类型,因此为调用方便我们把这一函数进行了重新定义如下。
function ExecuteFile(const FileName, Params, DefaultDir: String; ShowCmd: Integer): THandle; var zFileName, zParams, zDir: array[0..79] of Char; begin Result := ShellExecute(Application.MainForm.Handle, nil, StrPCopy(zFileName, FileName), StrPCopy(zParams, Params), StrPCopy(zDir, DefaultDir), ShowCmd); end;
以上函数在fmxutils单元中定义。fmxutils是一个自定义代码单元。 有关ShellExecute中各参数的具体含义读者可查阅联机Help文件。 StrPCopy把一个Pascal类型的字符串拷贝到一个无结束符的PChar类型字符串中。 在子窗口的Open1Click事件处理过程中:
procedure TFMForm.Open1Click(Sender: TObject); begin with FileList do ExecuteFile(FileName, '', Directory, SW_SHOW) ; end;
如果FileList允许显示目录的话(即FileType属性再增加一项ftDirectory),那么对于一个目录而言,打开的含义应该是显示它下边的子目录和文件。程序修改如下。
procefure TFMForm.Open1Click(Sender: Tobject); begin With FileList do begin if HasAttr(FileName,faDirectory) then DirectoryOutline.Directory := FileName else ExecuteFile(FileName,' ' ,Directory,SW_SHOW); end; end;
其中HasAttr是一个fmxutils单元中的自定义函数,用于检测指定文件是否具有某种属性。
function HasAttr(const FileName: String; Attr: Word): Boolean; begin Result := (FileGetAttr(FileName) and Attr) = Attr; end;
文件拷贝、移动、删除、更名
文件拷贝的关键是使用了以文件句柄为操作对象的文件管理函数,因而提供了一种底层的I/O通道。在Object Pascal中这一点是利用无类型文件实现的。 在文件拷贝中首先检查目标文件名是否是一个目录。如是则把原文件的文件名添加到目标路径后,生成目标文件全路径名。而后提取源文件的时间戳,以备拷贝完成后设置目标文件。拷贝过程中使用了返回文件句柄或以文件句柄为参数的文件管理函数FileOpen、FileCreate、FileRead、FileWrite、FileClose。为保证文件的正常关闭和内存的释放,在拷贝过程中进行异常保护。 过程CopyFile实现上述功能,它定义在fmxutils单元中。
procedure CopyFile(const FileName, DestName: TFileName); var CopyBuffer: Pointer; TimeStamp, BytesCopied: Longint; Source, Dest: Integer; Destination: TFileName; const ChunkSize: Longint = 8192; begin Destination := ExpandFileName(DestName); if HasAttr(Destination, faDirectory) then Destination := Destination + '\' + ExtractFileName(FileName); TimeStamp := FileAge(FileName); GetMem(CopyBuffer, ChunkSize); try Source := FileOpen(FileName, fmShareDenyWrite); if Source < 0 then raise EFOpenError.Create(FmtLoadStr(SFOpenError, [FileName])); try Dest := FileCreate(Destination); if Dest < 0 then raise EFCreateError.Create(FmtLoadStr(SFCreateError,[Destination])); try repeat BytesCopied := FileRead(Source, CopyBuffer^, ChunkSize); if BytesCopied > 0 then FileWrite(Dest, CopyBuffer^, BytesCopied); until BytesCopied < ChunkSize; finally FileSetDate(Dest,TimeStamp); FileClose(Dest); end; finally FileClose(Source); end; finally FreeMem(CopyBuffer, ChunkSize); end; end; 如果我们不使用FileSetDate过程,Windows自动把当前时间作为时间戳写入文件。 文件移动事实上是文件拷贝与文件删除的结合。fmxutils单元中的MoveFile过程实现了这一功能。
procedure MoveFile(const FileName, DestName: TFileName); var Destination: TFileName; begin Destination := ExpandFileName(DestName); if not RenameFile(FileName, Destination) then begin if HasAttr(FileName, faReadOnly) then raise EFCantMove.Create(Format(SFCantMove, [FileName])); CopyFile(FileName, Destination); DeleteFile(FileName); end; end;
EFCanMove是一个自定义异常类:
type EFCanMove := Class(EStreamError);
有关自定义异常类请参阅第十二章。 文件删除、文件更名直接调用Delphi文件管理过程DeleteFile、RenameFile。它们都以文件名为参数。操作执行前应弹出一个对话框进行确认,执行完毕后应调用Update方法更新FileList的显示。
一致的界面
文件拷贝、文件移动、 文件更名以及后边的改变当前目录在形式上都表现为从一个源文件到一个目标文件。因而可以采用统一的用户界面,即ChangeForm对话框(如图6.8)。
图6.8 ChangeForm对话框
这四个菜单项共用一个Click事件处理过程,通过对Sender参数的检测,决定将要打开对话框的标题和显示内容。当用户按OK键关闭且目标文件(目录)非空时,程序弹出一个消息对话框要求用户进一步确认,而后执行相应的动作。 共用的事件处理过程FileChange的程序清单如下:
procedure TFMForm.FileChange(Sender: TObject); var ChangeForm: TChangeForm; IsFile: Boolean; begin ChangeForm := TchangeForm.Create(Self); IsFile := True; with ChangeForm do begin if Sender = Move1 then Caption := 'Move' else if Sender = Copy1 then Caption := 'Copy' else if Sender = Rename1 then Caption := 'Rename' else if Sender = ChangeDirectory1 then begin Caption:='Change Directory'; IsFile:=False; end else Exit; if IsFile then begin CurrentDir.Caption := FileList.Directory; FromFileName.Text := FileList.FileName; ToFileName.Text := ''; end else begin CurrentDir.Caption := DriveTabSet.Tabs[DriveTabSet.TabIndex]; FromFileName.Text := DirectoryOutline.Directory; ToFileName.Text := ''; end; if (ShowModal <> idCancel) and (ToFileName.Text <> '') then ConfirmChange(Caption, FromFileName.Text, ToFileName.Text); end; end;
其中用到的自定义私有过程ConfirmChange用于执行相应的动作:
procedure TFMForm.ConfirmChange(const ACaption, FromFile, ToFile: String); begin if MessageDlg(Format('%s %s to %s', [ACaption, FromFile, ToFile]), mtConfirmation, [mbYes, mbNo], 0) = idYes then begin if ACaption = 'Move' then MoveFile(FromFile, ToFile) else if ACaption = 'Copy' then CopyFile(FromFile, ToFile) else if ACaption = 'Rename' then RenameFile(FromFile, ToFile) else if ACaption = 'Change Directory' then changeDirectory(ToFile); FileList.Update; end; end;
显示文件属性
当程序执行Properties 菜单项的Click 事件处理过程时,首先弹出一个TFileAttrForm类型的对话框,显示文件的属性(如图6.9)。
图6.9 FileAttrForm对话框
当用户修改并确认后程序重新设置文件属性。 Properties菜单项的Click事件处理过程如下:
procedure TFMForm.Properties1Click(Sender: TObject); var Attributes, NewAttributes: Word; FileAttrForm: TFileAttrForm; begin FileAttrForm := TFileAttrForm.Create(self); ShowFileAttr(FileAttrForm,FileList.FileName,FileList.Directory); end;
其中过程ShowFileAttr的实现如下:
procedure TFMForm.ShowFileAttr(FileAttrForm:TFileAttrForm; AFileName,Directory:String); var Attributes,NewAttributes: Word; begin with FileAttrForm do begin FileName.Caption := AFileName; FilePath.Caption := Directory; ChangeDate.Caption := DateTimeToStr(FileDateTime(AFileName)); Attributes := FileGetAttr(AFileName); ReadOnly.Checked := (Attributes and faReadOnly) = faReadOnly; Archive.Checked := (Attributes and faArchive) = faArchive; System.Checked := (Attributes and faSysFile) = faSysFile; Hidden.Checked := (Attributes and faHidden) = faHidden; if ShowModal <> idCancel then begin NewAttributes := Attributes; if ReadOnly.Checked then NewAttributes := NewAttributes or faReadOnly else NewAttributes := NewAttributes and not faReadOnly; if Archive.Checked then NewAttributes := NewAttributes or faArchive else NewAttributes := NewAttributes and not faArchive; if System.Checked then NewAttributes := NewAttributes or faSysFile else NewAttributes := NewAttributes and not faSysFile; if Hidden.Checked then NewAttributes := NewAttributes or faHidden else NewAttributes := NewAttributes and not faHidden; if NewAttributes <> Attributes then FileSetAttr(AFileName, NewAttributes); end; end; end;
以上过程中用到的函数FileDataTime在fmxutils单元中定义,返回一个TDatatime类型的变量。
function FileDateTime(const FileName: String): System.TDateTime; begin Result := FileDateToDateTime(FileAge(FileName)); end;
其它文件管理功能的实现
在子窗口的Function菜单中,定义了一些其它的文件管理功能: ● Search :查找一个给定名字的文件,若存在则显示该文件属性 ● Disk View :显示当前驱动器的大小和剩余空间 ● View type :确定显示文件的类型
文件查找
当用户单击Search菜单项时,程序弹出一个对话框(如图6.10),要求输入待查找的文件名和查找路径。文件名可以是通配符。当用户确认后程序显示第一个匹配文件的属性(如图6.9)。查找不到匹配文件则给出相应的信息。
图6.10 SearchForm对话框
在实现这一功能的最初设计中,我试图使用FileSearch函数,这个函数允许在多个不同路径中查找。但可惜的是:也许由于系统设计者的失误,这个函数并没有返回它应该返回的东西(第一个匹配文件的全路径名),而是仍把输入的匹配符返回。 没有办法我只能再次使用FindFirst,这个函数的特性在6.3节中已进行了介绍。下面是这一功能的实现代码。
procedure TFMForm.search1Click(Sender: TObject); var SearchForm: TSearchForm; FileAttrForm: TFileAttrForm; FindIt,path: String; SearchRec: TSearchRec; Return: Integer; begin SearchForm := TSearchForm.Create(self); with SearchForm do begin SearchFile.text := ''; SearchPath.text := DirectoryOutline.Directory; if (ShowModal <> idCancel) and (SearchFile.Text <> '') and (SearchPath.text <> '') then begin FindIt := SearchPath.text+'\'+SearchFile.text; Return := FindFirst(FindIt,faAnyFile,SearchRec); if Return <> 0 then FindIt := '' else FindIt := ExpandFileName(SearchRec.Name); end; if FindIt = '' then MessageDlg('Cannot find the file in current directory.', mtWarning, [mbOk], 0) else begin Path := ExtractFilePath(FindIt); FindIt := ExtractFileName(FindIt); FileAttrForm := TFileAttrForm.Create(self); ShowFileAttr(FileAttrForm,FindIt,Path); end; end; end;
显示磁盘信息
当用户单击Disk View菜单项时,将弹出一个TDiskViewForm类型的对话框,用来显示当前磁盘的信息(如图6.11)。
图6.11 DiskViewForm对话框
磁盘信息的获取是在DiskViewForm中DriveEdit编辑框的OnChange事件处理过程中实现的。
procedure TDiskViewForm.driveEditChange(Sender: TObject); var dr: Byte; Free,Total: LongInt; begin Free := DiskFree(0); Total := DiskSize(0); FreeSpace.text := IntToStr(Free)+ ' bytes.'; TotalSpace.text := IntToStr(Total) + ' bytes.'; end;
DiskFree、DiskSize带参数为0表示当前驱动器。读者可以很容易把它改成按用户输入显示磁盘信息的情况。 DiskViewForm中的三个编辑框设计时都令ReadOnly为True。
改变显示文件的类型
改变显示文件的类型事实上是设置FileList的Mask属性。我们利用一个标准的InputBox输入文件的匹配字符串。而后利用Update方法更新FileList。
procedure TFMForm.Viewtype1Click(Sender: TObject); var FileMask: String; begin FileMask := InputBox('File type','Input File type For View :',FileList.Mask); If FileMask = '' then FileMask := '*.*'; FileList.Mask := FileMask; FileList.Update; CreateCaption; end;
其中的CreateCaption私有过程将在(6.4.8)中进行介绍。
目录管理功能的实现
在子窗口的Directory菜单中,提供了目录管理功能: ● Create Directory :创建一个子目录 ● Delete Directory :删除一个空的子目录 ● Change Directory :改变当前目录
创建目录
创建目录时首先弹出一个TNewDir类型的对话框(如图6.12)。
图6.12 NewDir对话框 对话框中要求用户输入目录名。如果用户不输入路径,则缺省认定为当前目录的子目录:
Dir := ExpandFileName(DirName.Text);
而后调用MkDir函数。在目录创建过程中关闭了I/O错误检测,出错不产生异常而是把IOResult设置为非零值。通过检查IOResult是否为0可以确定创建是否成功。 程序清单如下:
procedure TFMForm.CreateDirectory1Click(Sender: TObject); var NewDir: TNewDir; Dir: String; begin {$I-} NewDir := TNewDir.Create(self); with NewDir do begin CurrentDir.Caption := DirectoryOutline.Directory; if (ShowModal <> idCancel) and (DirName.Text <> '') then Dir := ExpandFileName(DirName.text); end; MkDir(Dir); if IOResult <> 0 then MessageDlg('Cannot Create directory', mtWarning, [mbOk], 0); end;
但不幸的是目录创建后我们却无法从当前目录树中看到。必须移到另一个驱动器而后再返回,创建的目录才是可见的。在后边我们将提供一种解决方法。
删除目录
在实现目录删除过程中,远不如创建目录那么顺利。碰到的问题是: 1.RmDir不允许删除当前目录。但为了操作方便,我们要求删除的恰恰是当前目录; 2.目录删除后调用Refresh方法或Update方法并不能使该目录从屏幕显示中去除。因而当用户试图进入该目录时会导致系统崩溃。 对第一个问题,我们的解决办法是把当前目录转换到其父目录。假如读者记得目录也被操作系统作为一种特殊的文件对待的话,那么就不会对下面的语句感到奇怪了:
path := DirectoryOutline.Directory; Directoryoutlin.Directory := ExpandFilePath(Path);
而后调用RmDir过程:
RmDir(Path);
第二个问题的解决却颇为费神。因为DirectoryOutline是Delphi提供的示例部件,没有Help文件支持。通过试验发现:只有当DirectoryOutline的Drive属性改变时,才重新从相应驱动器读取目录。而且它基本上是只读的,除非清除( Clear) 它,象Add、Delete这些方法对它都是无效的。 我曾经考虑过一个笨拙的方法,那就是先改变当前驱动器而后再改回来。但这种方法一方面速度无法忍受,另一方面当只存在一个驱动器可用时会导致系统崩溃。 正当我一筹莫展时,突然想到:DirectoryOutline是一个Sample部件,Delphi 提供了它的源代码。而当我分析了它的源代码后,我知道应该做什么了,那就是为DirectoryOutline增添一个Reset方法!
为部件增添一个方法
严格地说,我们所做的工作属于创建一个新部件。但因为我们有源代码,所以不必从DirectoryOutline继承而是直接修改它。这样我们可以省去与创建部件有关的许多繁琐工作。对创建新部件感兴趣的读者可阅读本书第三编的有关章节。 在Delphi IDE中打开DirectoryOutline的源文件后: 1.把库单元名改为DirPlus,类名改为TDirectoryOutlinePlus,表明这是DirectoryOutline的增强版。而后存入另一个目录中; 2.添加一个公有方法Reset。这一方法的作用是重新读取当前驱动器的目录。程序清单如下。
procedure TDirectoryOutlinePlus.Reset; begin ChDir(FDrive + ':'); GetDir(0, FDirectory); FDirectory := ForceCase(FDirectory); if not (csLoading in ComponentState) then BuildTree; end;
读者也许被这段代码弄糊涂了。由于篇幅所限,而且涉及到许多自定义部件开发的内容,我们也不准备去详细解释它。假如读者想彻底搞懂它,我建议先看一下本书第三编有关自定义部件开发的内容,而后再对照原DirectoryOutline的源代码进行分析。 3.编译成一个库文件DirPlus.tpu; 4.把DirPlus加入部件的Samples页中。 如何添加一个部件见第三编有关章节的介绍。 当增强的目录树准备好以后,必须修改我们的子窗口设计,但却不必亲自修改源代码。 1.删除子窗口中的TDirectoryOutline类部件DirectoryOutline。此时FileList占据了整个客户区; 2.把FileList的Align属改为None,并留出左边的空白供放部件用; 3.在窗口左部加入TDirectoryOutlinPlus类的部件DirectoryOutline; 4.把DirectoryOutline的Align属性改为Left,FileList的Align属性还原为Client; 5.在DirectoryOutline的事件OnChange列表中选取DirectoryOutlineChange,即原DirectoryOutline的处理过程。 以上工作的最终目标是实现目录创建、删除后屏幕的正确显示。这只需要调用DirectoryOutline的Reset方法即可。 目录删除过程的实现代码如下。
procedure TFMForm.DeleteDirectory1Click(Sender: TObject); var path: String; k: Integer; begin {$I-} path := DirectoryOutline.Directory; DirectoryOutline.Directory := ExtractFilePath(Path); if MessageDlg('Delete ' + path + '?', mtConfirmation,[mbYes, mbNo], 0) = idYes then RmDir(path); if IOResult <> 0 then MessageDlg(' Cannot remove directory! The path might not'+ 'exist,non-empty or is the current logged directory.',mtWarning,[mbOk], 0) else DirectoryOutline.Reset; end; 修改后的目录创建过程如下。
procedure TFMForm.CreateDirectory1Click(Sender: TObject); var NewDir: TNewDir; Dir: String; begin {$I-} NewDir := TNewDir.Create(self); with NewDir do begin CurrentDir.Caption := DirectoryOutline.Directory; if (ShowModal <> idCancel) and (DirName.Text <> '') then Dir := ExpandFileName(DirName.text); end; MkDir(Dir); if IOResult <> 0 then MessageDlg('Cannot Create directory', mtWarning, [mbOk], 0) else DirectoryOutline.Reset; end;
当完成了这些工作,把程序重新编译、运行后,可以发现我们所希望实现的功能完全实现了!同时,我们有了一个更好的目录树部件。
改变当前目录
改变当前目录的实现非常简单,只要修改DirectoryOutline的Directory属性。但需注意的是:当改变后目录所在驱动器也发生变化时应相应修改DriveTabSet的当前值。由于驱动器名与DriveTabSet的索引属性TabIndex之间并没有确定的对应关系,因而需要通过一个循环进行查找匹配。 Change Directory的菜单事件处理过程是FileChange,即与文件的移动、拷贝、更名共用一个事件处理过程。详细情况请读者参看(6.4.5.3)中的介绍。 改变当前目录的实现如下。
procedure TFMForm.ChangeDirectory(Todir: String); var i: Integer; begin {$I-} ChDir(ToDir); if IOResult <> 0 then MessageDlg('Cannot find directory', mtWarning, [mbOk], 0) else begin with DirectoryOutline do begin Directory := ToDir; Refresh; if DriveTabSet.Tabs[DriveTabSet.TabIndex][1]<>drive then for I := 1 to 25 do if DriveTabSet.Tabs[i][1] = drive then begin DriveTabSet.TabIndex := i; Exit; end; end; end; end; 一些问题的处理
子窗口的标题
Windows的文件管理器是我们设计的楷模,在子窗口显示标题上也不例外。我们把当前目录加上文件的类型作为子窗口的标题。 过程CreateCaption用于生成子窗口的标题。
procedure TFMForm.CreateCaption; var Cap: String; begin Cap := DirectoryOutline.Directory; Cap := cap+'\'+FileList.mask; Caption := Cap; end;
当前目录或文件显示类型发生变化时改变子窗口的标题。如DirectoryOutline的Change事件处理过程和ViewType菜单项的Click事件处理过程就调用了该过程。
状态条的显示
状态条用于显示当前目录和当前选中文件。它们的值在DirectoryOutline 和FileList的Change事件处理过程中修改。 DirectoryOutline和FileList最终的Change事件处理过程如下:
procedure TFMForm.DirectoryOutlineChange(Sender: TObject); begin CreateCaption; FileList.clear; FileList.Directory := DirectoryOutline.Directory; FileList.Update; FileManager.DirectoryPanel.Caption := DirectoryOutline.Directory; end; procedure TFMForm.FileListChange(Sender: TObject); begin with FileList do begin if (ItemIndex >= 0) and (Not HasAttr(FileName,faDirectory)) then begin TheFileName := FileName; FileManager.FilePanel.Caption := Format('%s, %d bytes', [TheFileName, GetFileSize(TheFileName)]); end else FileManager.FilePanel.Caption := ''; end; end;
版本信息
当用户单击主窗口的Help|About菜单项时将弹出一个About对话框,用于显示版本信息(如图6.13)。 这一对话框是用Delphi提供的模板做的。
图6.13 版本信息
菜单项的变灰与使能
File菜单中定义的文件管理功能只有当活动焦点在FileList(即有当前选中文件)时才起作用。否则所有菜单项应变灰,以免导致系统崩溃。 这一功能在File菜单的Click事件处理过程中实现。这一点并不很容易被人想到,希望读者能从中受到启发。
procedure TFMForm.File1Click(Sender: TObject); var FileSelected: Boolean; begin FileSelected := FileList.ItemIndex >= 0; Open1.Enabled := FileSelected; Delete1.Enabled := FileSelected; Copy1.Enabled := FileSelected; Move1.Enabled := FileSelected; Rename1.Enabled := FileSelected; Properties1.Enabled := FileSelected; end;
判断是否有文件被选中是通过检测ItemIndex属性是否大于等于0来实现的。 FileSelected := FileList.ItemIndex >= 0;
可重用的文件处理模块
库单元fmxutils是一个代码库,提供了若干文件处理模块。这些模块除在本程序中使用外,读者可以在其它应用程序中直接调用,而且不必重新编译,只要在Uses子句中包含即可。从中我们可以体会到,Delphi 以库单元为中心的程序组织方式提供了一种较完善的代码重用机制。
小结
文件管理器是一个较为综合的例程,使用到了绝大部分以文件名、文件句柄以及其它参数(除文件变量)为操作对象的文件管理过程/函数,同时也提供了一些程序设计开发的思想。我们的介绍是以程序功能模块来组织的,我建议读者在学习并试图自己建立这一程序时采用同样的方法。这是一开始就应了解的,但其它完全可以按顺序逐步地扩充,最后得到一个完整的程序。这一例程在后边的拖放操作和异常处理等章节中还要用到。读者可以以此为基础进一步完善它,使它真正成为一个完全实用的程序。 文件管理是在开发一个高级的Windows程序中不可避免的要涉及到的问题。本章介绍的思路和方法将为读者成为一个熟练的程序员奠定基础。
|
||||||||||||||||