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;
6.4.5 文件管理基本功能的实现
在子窗口的File菜单中,定义了文件管理的基本功能,它们是:
● Open :打开或运行一个文件( 从文件列表框双击该文件可实现同样效果)
● Move :文件在不同目录间的移动
● Copy :文件拷贝
● Delete :文件删除
● Rename :文件更名
● Properties :显示文件属性
6.4.5.1 文件打开
文件打开功能可以运行一个可执行文件,或把文件在与之相关联的应用程序中打开。文件总是与创建它的应用程序相关联,这种关联可以在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;
6.4.5.2 文件拷贝、移动、删除、更名
文件拷贝的关键是使用了以文件句柄为操作对象的文件管理函数,因而提供了一种底层的I/O通道。在Object Pascal中这一点是利用无类型文件实现的。FileOpen、FileCreate、FileRead、FileWrite、FileClose。为保证文件的正常关闭和内存的释放,在拷贝过程中进行异常保护。
在文件拷贝中首先检查目标文件名是否是一个目录。如是则把原文件的文件名添加到目标路径后,生成目标文件全路径名。而后提取源文件的时间戳,以备拷贝完成后设置目标文件。拷贝过程中使用了返回文件句柄或以文件句柄为参数的文件管理函数
过程CopyFile实现上述功能,它定义在fmxutils单元中。#p#分页标题#e#
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的显示。
6.4.5.3 一致的界面
文件拷贝、文件移动、 文件更名以及后边的改变当前目录在形式上都表现为从一个源文件到一个目标文件。因而可以采用统一的用户界面,即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;
6.4.5.4 显示文件属性
当程序执行Properties 菜单项的Click 事件处理过程时,首先弹出一个TFileAttrForm#p#分页标题#e#类型的对话框,显示文件的属性
当用户修改并确认后程序重新设置文件属性。
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;
6.4.6 其它文件管理功能的实现
在子窗口的Function菜单中,定义了一些其它的文件管理功能:
● Search :查找一个给定名字的文件,若存在则显示该文件属性
● Disk View :显示当前驱动器的大小和剩余空间
● View type :确定显示文件的类型
6.4.6.1 文件查找
当用户单击Search菜单项时,程序弹出一个对话框(如图6.10) ,要求输入待查找的文件名和查找路径。文件名可以是通配符。当用户确认后程序显示第一个匹配文件的属性(如图6.9) 。查找不到匹配文件则给出相应的信息。
在实现这一功能的最初设计中,我试图使用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;
6.4.6.2 显示磁盘信息
当用户单击Disk View菜单项时,将弹出一个TDiskViewForm类型的对话框,用来显示当前磁盘的信息
磁盘信息的获取是在DiskViewForm中DriveEdit编辑框的OnChange事件处理过程中实现的。#p#分页标题#e#
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。
6.4.6.3 改变显示文件的类型
改变显示文件的类型事实上是设置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)中进行介绍。
6.4.7 目录管理功能的实现
在子窗口的Directory菜单中,提供了目录管理功能:
● Create Directory :创建一个子目录
● Delete Directory :删除一个空的子目录
● Change Directory :改变当前目录
6.4.7.1 创建目录
创建目录时首先弹出一个TNewDir类型的对话框
对话框中要求用户输入目录名。如果用户不输入路径,则缺省认定为当前目录的子目录:
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;
但不幸的是目录创建后我们却无法从当前目录树中看到。必须移到另一个驱动器而后再返回,创建的目录才是可见的。在后边我们将提供一种解决方法。
6.4.7.2 删除目录
在实现目录删除过程中,远不如创建目录那么顺利。碰到的问题是:
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方法!
6.7.3 为部件增添一个方法
严格地说,我们所做的工作属于创建一个新部件。但因为我们有源代码,所以不必从DirectoryOutline继承而是直接修改它。这样我们可以省去与创建部件有关的许多繁琐工作。对创建新部件感兴趣的读者可阅读本书第三编的有关章节。Delphi IDE中打开DirectoryOutline的源文件后:
在
1. 把库单元名改为DirPlus,类名改为#p#分页标题#e#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;
当完成了这些工作,把程序重新编译、运行后,可以发现我们所希望实现的功能完全实现了!同时,我们有了一个更好的目录树部件。
6.4.7.4 改变当前目录
改变当前目录的实现非常简单,只要修改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;#p#分页标题#e#
end;
end;
6.4.8 一些问题的处理
6.4.8.1 子窗口的标题
Windows的文件管理器是我们设计的楷模,在子窗口显示标题上也不例外。我们把当前目录加上文件的类型作为子窗口的标题。
过程CreateCaption用于生成子窗口的标题。
procedure TFMForm.CreateCaption;
var
Cap: String;
begin
Cap := DirectoryOutline.Directory;
Cap := cap+'\'+FileList.mask;
Caption := Cap;
end;
当前目录或文件显示类型发生变化时改变子窗口的标题。如DirectoryOutline的Change事件处理过程和ViewType菜单项的Click事件处理过程就调用了该过程。
6.4.8.2 状态条的显示
状态条用于显示当前目录和当前选中文件。它们的值在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;
6.4.8.3 版本信息
当用户单击主窗口的Help|About菜单项时将弹出一个About对话框,用于显示版本信息(如图6.13)。Delphi提供的模板做的。
这一对话框是用
6.4.8.4 菜单项的变灰与使能
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;
6.4.8.5 可重用的文件处理模块
库单元fmxutils是一个代码库,提供了若干文件处理模块。这些模块除在本程序中使用外,读者可以在其它应用程序中直接调用,而且不必重新编译,只要在Uses子句中包含即可。从中我们可以体会到,Delphi 以库单元为中心的程序组织方式提供了一种较完善的代码重用机制。
6.4.9 小结
文件管理器是一个较为综合的例程,使用到了绝大部分以文件名、文件句柄以及其它参数( 除文件变量)为操作对象的文件管理过程/ 函数,同时也提供了一些程序设计开发的思想。我们的介绍是以程序功能模块来组织的,我建议读者在学习并试图自己建立这一程序时采用同样的方法。(6.4.8)中的内容或许是一开始就应了解的,但其它完全可以按顺序逐步地扩充,最后得到一个完整的程序。这一例程在后边的拖放操作和异常处理等章节中还要用到。读者可以以此为基础进一步完善它,使它真正成为一个完全实用的程序。Windows程序中不可避免的要涉及到的问题。本章介绍的思路和方法将为读者成为一个熟练的程序员奠定基础。
文件管理是在开发一个高级的
这一功能在
没有办法我只能再次使用
文件打开功能实现的关键是利用了
评论 {{userinfo.comments}}
{{child.content}}
{{question.question}}
提交