分享到新浪微博 分享到QQ空间 打印

DELPHI基础教程

DELPHI基础教程

DELPHI基础教程

第一章 Delphi快速入门(一)

Delphi是全新的可视化编程环境,为我们提供了一种方便、快捷的Windows应用程序开发工具。它使用了Microsoft Windows图形用户界面的许多先进特性和设计思想,采用了弹性可重复利用的完整的面向对象程序语言(Object-Oriented Language)、当今世界上最快的编辑器、最为领先的数据库技术。对于广大的程序开发人员来讲,使用Delphi开发应用软件,无疑会大大地提高编程效率,而且随着应用的深入,您将会发现编程不再是枯燥无味的工作——Delphi的每一个设计细节,都将带给您一份欣喜。 

1.1 Delphi基本概念介绍 

1.1.1 Delphi的基本形式 

Delphi实际上是Pascal语言的一种版本,但它与传统的Pascal语言有天壤之别。一个Delphi程序首先是应用程序框架,而这一框架正是应用程序的“骨架”。在骨架上即使没有附着任何东西,仍可以严格地按照设计运行。您的工作只是在“骨架”中加入您的程序。缺省的应用程序是一个空白的窗体(Form),您可以运行它,结果得到一个空白的窗口。这个窗口具有Windows窗口的全部性质:可以被放大缩小、移动、最大最小化等,但您却没有编写一行程序。因此,可以说应用程序框架通过提供所有应用程序共有的东西,为用户应用程序的开发打下了良好的基础。Delphi已经为您做好了一切基础工作——程序框架就是一个已经完成的可运行应用程序,只是不处理任何事情。您所需要做的,只是在程序中加入完成您所需功能的代码而已。

在空白窗口的背后,应用程序的框架正在等待用户的输入。由于您并未告诉它接收到用户输入后作何反应,窗口除了响应Windows的基本操作(移动、缩放等)外,它只是接受用户的输入,然后再忽略。Delphi把Windows编程的回调、句柄处理等繁复过程都放在一个不可见的Romulam覆盖物下面,这样您可以不为它们所困扰,轻松从容地对可视部件进行编程。

1.1.2 面向对象编程的概念 

面向对象的程序设计(Object-Oriented Programming,简记为OOP)是Delphi诞生的基础。OOP立意于创建软件重用代码,具备更好地模拟现实世界环境的能力,这使它被公认为是自上而下编程的优胜者。它通过给程序中加入扩展语句,把函数“封装”进Windows编程所必需的“对象”中。面向对象的编程语言使得复杂的工作条理清晰、编写容易。说它是一场革命,不是对对象本身而言,而是对它们处理工作的能力而言。对象并不与传统程序设计和编程方法兼容,只是部分面向对象反而会使情形更糟。除非整个开发环境都是面向对象的,否则对象产生的好处还没有带来的麻烦多。而Delphi是完全面向对象的,这就使得Delphi成为一种触手可及的促进软件重用的开发工具,从而具有强大的吸引力。

一些早期的具有OOP性能的程序语言如C++,Pascal,Smalltalk等,虽然具有面向对象的特征,但不能轻松地画出可视化对象,与用户交互能力较差,程序员仍然要编写大量的代码。Delphi的推出,填补了这项空白。您不必自己建立对象,只要在提供的程序框架中加入完成功能的代码,其余的都交给Delphi去做。欲生成漂亮的界面和结构良好的程序丝毫不必绞尽脑汁,Delphi将帮助您轻松地完成。它允许在一个具有真正OOP扩展的可视化编程环境中,使用它的Object Pascal语言。这种革命性的组合,使得可视化编程与面向对象的开发框架紧密地结合起来。

1.2 Delphi 快速入门 

在这一节中,我们来开发一个小程序。随着开发的过程,逐步介绍Delphi的主要部件及其操作方法。建议读者按照本书介绍的过程,在您的电脑上直接操作。您将对Delphi的可视化编程有一个直观、快捷的了解,必将起到事半功倍的效果。 

1.2.1 进入Delphi的可视化编程环境

1.2.1.1 安装Delphi 

Delphi的安装与其它应用软件并无不同。2.0版必须在Windows 95以上的操作系统中使用。启动Windows 95或Windows NT后,将Delphi的光盘放入光驱(CD-ROM)中,运行光盘上的\INSTALL\SETUP.EXE文件,它的安装程序会提示您正确地装入Delphi。如果您是在微软中文Windows环境中安装Delphi,请参照附录A来设置您的BDE环境,以便于处理中文数据。 

1.2.1.2 进入Delphi 环境

  为避免隐藏在Delphi后的Program Manager和曾经运行过的其它程序扰乱版面,分散您的注意力,不妨在启动Delphi前关掉其它应用程序;启动Delphi后,再最小化隐藏在后面的Delphi 2.0程序组。这样屏幕上就只留下Delphi窗口可见了。

首次加载Delphi,屏幕上会出现四个窗口:

● 标题为“Delphi-Project1”的Delphi主窗口

● Object Inspector窗口

● 标题为“Form1”的窗体(Form)窗口

● 标题为“Unit1.PAS”的代码编辑窗口。刚启动时这一窗口的大部分被“Form1”窗体所掩盖。将“Form1”窗体移开,或单击Form1窗体下方的状态行,可以使其全部可见。在“Form1”窗体的任意可见位置单击鼠标,可以恢复主窗体可见

以下我们将对这四个窗口分别进行介绍。 

1.2.2 Delphi可视化编程环境介绍 

1.2.2.1 主窗口(Main Form) 

Delphi的主窗口位于屏幕的上端,包括Menu(菜单)、Speed Bar(加速条)和Component Panel(部件选项板)。Menu是下拉式主菜单。Speed Bar位于主窗口的左下端,由两排共14个加速按钮组成。这些按钮是菜单功能的快捷方式,各种图标直观地表示了它能执行的动作。Component Panel由一行、若干页对象按钮所组成,利用它来选择需要的部件并将它放到窗体中去。 

1.2.2.2 Object Inspector(对象检视器) 

Object Inspector窗口含有两页:Properties页显示窗体中当前被选择部件的属性信息,并允许改变对象的属性;Events页列出了当前部件可以响应的事件。按动Object Inspector下端的“Events”页标签,使得Events页可见,这一定的事件后边的空白处,可以定义对象接受到相应事件时执行的动作。首次启动时,Object Inspector窗口显示的是当前窗体Form1的属性。Object Inspector根据对象属性的多少,决定是否有滚行显示。移动滚行条,可以查看当前对象的全部属性。

  此外,Object Inspector上还有Object Selector(对象选择器),位于Object Inspector上方的下拉式菜单中。它显示了窗体上所有部件的名称和类型,也包含窗体本身。您可以用Object Selector很容易地在窗体的各个部件之间切换,也可以快速地回到窗体本身。当窗体中含有较多的对象时,您会发现这是切换对象尤其是回到窗体的最快捷途径。

   想使Object Inspector一直可见,可将鼠标移到Object Inspector上,按动右键,以启动Object Inspector的弹出式菜单,将其设置为Stay On Top。这对初学者常是一个很重要的设置方式。 

1.2.2.3 窗体窗口 

Forms窗口是开展大部分设计的工作区域。首次启动Delphi 2.0时显示的是窗体Form1。可以把部件放在窗体中,通过移动位置、改变尺寸等操作随心所欲地安排它们,以此来开发应用程序的用户界面。您可以把窗体想象成一个可以放置其它部件的容器。窗体上有栅格(Grids),供放置部件时对齐位置用,在程序运行时Grids是不可见的。

     一个真正的应用程序可能有不止一个窗口,您可以选用不同的窗体进行设计。其它窗体可以是对话框(Dialog Box)、数据录入框等。 

1.2.2.4 代码窗口 

  代码窗口一开始处于窗体窗口之下。因为在Delphi中,设计用户界面直接在窗体中进行,运行结果和设计样板完全一致。当部件被放到窗体上时,Delphi会自动生成大部分的用户界面代码。您所应做的只是在它为您生成的框架中加入完成所需功能的程序段而已。点动Form1的状态行使代码窗口可见。

这个窗口中是代码编辑器。可以在其中书写Delphi应用程序的源代码。当程序中含有不止一个窗口时,会有几个库单元的源程序出现在代码编辑器中。代码编辑器的标题条中显示了当前正在编辑的库单元文件名。要查看某一特定程序的源代码,只需用鼠标点动写有该库单元文件名的页标签,就可以对该库单元进行编辑了。


想死你们了!

TOP

第一章 Delphi快速入门(二)

1.2.3 设计简单的用户界面 

首先从空白窗体开始工作。我们将设计一个简单的程序:在屏幕上开一个窗口,窗口中有一个图框,用三个按钮来改变图框的形状;再用一个图标按钮来进行图框的颜色设置,通过颜色编辑对话框来选择变成哪一种颜色。

在菜单上选用File|New Application菜单项来生成新的工程文件。这时,屏幕上出现图1.2所示的窗体。 

1.2.3.1 选取部件加入到窗体中 

部件(Component)是建立Delphi应用程序的要素。Delphi为用户提供了丰富的部件库,既有可视的部件(如编辑框、按钮)等,也有不可视的部件(如系统定时器、数据表等)。它们按照功能分别排列在Component Panel的各页上。

移动鼠标到Component Panel上,在部件按钮上“犹豫”一、两秒钟,一个黄色小提示框就会弹出,写有该部件的名称,我们称之为提示(Hint)。在要选择的部件上单击左键,则该部件按钮被按下,表示部件已被当前选择。然后,将鼠标移动到窗体上,按下左键,该部件被放到窗体中。部件的轮廓线上会显现八个被称为尺寸调整器(Sizing Handles)的黑色小方块。它除了供用户调整尺寸使用之外,还可以表示该对象处于当前编辑状态。此时,按“Delete”键可以将该部件删除。

  在Component Panel上,点动写有“Additional”的页标签,切换到Additional页,再移动鼠标,逐个用观看Hint的办法查看部件的名称,找到“Shape”部件(其图标为圆、方形、三角形三个几何体),将其放到窗体的左方。

再按动“Standard”页标签,找到“Button”部件(图标上画有OK按钮)。Delphi允许在添加多个同类型的部件时,不必每次到部件选项板上选取。按住“Shift”键,同时在“Button”部件上单击鼠标左键,这时“Button”部件处于按下状态,并有蓝色边框,表示已经被选择固定。依次在窗体右方的三个位置单击左键,则会放置三个相同的Button部件。把鼠标光标移到部件选项板左侧的箭头图标处,这是“取消选择”按钮,它没有提示。按动它,会发现“Button”部件按钮恢复弹起状态。否则,每次“Form1”上的鼠标单击左键动作,都会导致增加一个按钮部件。

  再次进入“Additional”部件页,选择一个“BitBtn”按钮(图标是有绿色对号的OK按钮),把它放到其它三个按钮部件的下面。

点动“Dialog”页标签,选择以16色网格做图标的ColorDialog部件并把它放到窗体的任意位置。因为这一部件是不可视部件,所以它的位置并不影响大局。 

1.2.3.2 部件的调整与对齐 

一般来讲,此时放置到窗体的部件是分散排列的,而且其大小不是真正需要的尺寸。除了不可视的ColorDialog部件外,其它的部件都需要进行位置、大小和显示字样的调整。Delphi提供的对齐工具和窗口栅格为这些调整提供了方便。

1、移动部件

只需把鼠标落到想移动的部件上,按住左键并移动光标,到合适的位置再释放左键,整个部件即被移到新位置。

2、调整尺寸

先把鼠标光标移动到要改变尺寸的部件上,单击左键,选中该部件,尺寸调整器会出现,把鼠标移动到其中的一个小黑色方块上,当鼠标变成拖动方向指示时,按下并拖动鼠标左键,可以放大或缩小部件。上下左右的小方块用来移动对应的各边,四个角的方块可以移动相连的两条边。如果要精确地表述部件的尺寸,可以在Object Inspector上,改变Left(表示部件左边缘到窗体左边框的象素点数)、Top(表示窗体上边框到部件上边缘的象素点数)、 Width(部件本身的宽度)、Height(部件本身的高度)等属性。关于改变部件的属性,下文还将仔细讲解。

3、使得一组部件对齐

用调整位置的方法可以对齐部件,但操作步骤复杂。Delphi提供的对齐工具可以使多个部件的对齐极为迅速方便。下面我们来使四个按钮对齐。先将四个按钮选为一组:按住并向右下方拖动鼠标左键,在窗体上画出围绕四个按钮的矩形,释放左键后,被选中的按钮周边会出现暗灰色的边框。选用Edit|Align命令,或使用弹出式菜单(在被固定的部件上单击鼠标右键激活),可以显示图1.4所示的对话框。

要使按钮沿左边对齐并使它们在垂直方向上均匀分布,先在Alignment对话框的Horizontal栏内选择“Left sides”,在“Vertical”栏内选择“Space equally”,按动OK按钮,Delphi就会自动将它们对齐。然后,您可以将它们四个作为一组来移动。在四个按钮以外的窗体上按动鼠标左键,就释放了组中的部件,使它们成为分立的部件。

利用对齐模板来对齐部件也是很方便的。首先要将要对齐的部件选成一组,选择View|Alignment Palette显示对齐模板。按照所示的方式选择即可达到对齐的目的

4、锁定部件

如果部件已经对齐,为防止不小心移动部件,可以将部件位置锁定。选择主菜单上的Edit|Lock Controls选项,使得部件不能进行移动操作。解锁只需再次选择此项即可。

调整“Shape”部件的大小,使之与右边的按钮组相匹配。再改变窗体的大小,按住并拖动窗体右下方使之刚好包容窗体上的全部部件。这样,您的用户界面就会比较美观。 

1.2.3.3 保存所做的工作 

及时地保存所做的工作至关重要。对设计者来讲,有两个文件需要保存:库单元文件(以.PAS为后缀)和工程文件(以.DPR为后缀)。

从主菜单上选择File|Save Project As...项,Delphi会显示标题为“Save Unit1 As”的文件保存对话框,Delphi 2.0 允许用户更改存储路径,您可以在下拉式列表框中选择。最好将您的文件保存在自己的目录中。在编辑框中键入demoform.pas以保存库单元文件;然后显示标题为“Save Project As”的另一个文件保存对话框,键入sample.dpr。Delphi保存这两个文件并返回窗体窗口。不要把库单元和工程存成一样的文件名,Delphi要求两者不同。

第一次保存后,以后可以随时通过Speed Bar中的“Save All”和“Save file”来保存工程文件和库单元文件。一般来讲,当确认文件的改变后,要同时存储这两个文件。 

1.2.3.4 运行工程 

  以上的操作使您有了一个自己的应用程序界面。在速度条中按动“Run”按钮(绘有绿色三角图标),您可以看到,所生成的界面与您设计的界面是完全一致的。 

1.2.4 改变对象的属性

  上述的工程虽能够运行,但它对您的按动按钮操作是没有什么反应的,而且,所有部件上还写着我们不需要的字样。双击窗口的关闭按钮结束运行,回到设计界面。下面,我们将仔细讲述如何在Object Inspector中改变部件的各种属性。 

1.2.4.1 用Properties页改变部件的属性值 

首先要改变各种部件的标题。先给窗口命名为“Demo”。按动Object Inspector上端的Object Selector的题条或者其右端的下拉标志,找到Form1项,并点动左键,窗体被选中。在Object Inspector的Properties页中,找到Caption属性并用左键选中,将其右端的Form1改为Demo,同时,您会发现窗体的标题已经相应地做了改变。

用鼠标点中窗体中的Shape部件,Object Inspector列出了它的属性。选中Shape属性,您会发现右端出现了下拉标志。点动这一标志,可以查看对象的Shape属性可选值。它的形状可以是矩形、圆形、圆角矩形、方形等几种。这是我们设计后续功能的基础。

选中Button1按钮,此时Object Inspector已经显示出此按钮的一应属性。将它的Caption属性改为“&Rectangle”,“&”号使得Delphi特殊处理它后面的字符,在这里,按钮中的R字母被做了下划线处理,运行时,可以用“Alt-R”热键来按动这一按钮。同样,您可以将其它的两个按钮Button2和Button3的Caption属性改成需要的形状指示,譬如“&RoundRec”、“&Ellipse”。 

1.2.4.2 设置窗体的缺省按钮 

可以把某个按钮作为窗口上的缺省按钮,Delphi会为按钮加上有黑色的边缘。运行时,回车即相当于被按下。只需将此按钮的Default属性从False改成True,即将它设为窗体的缺省按钮。点动Default属性,在右端的值后面双击左键,或从下拉菜单中选取True,即可改变这一属性。Delphi中有许多只有True、False两个属性的部件,双击左键可以在这两个值之间切换。


想死你们了!

TOP

DELPHI基础教程

第一章 Delphi快速入门(三)

1.2.4.3 汉化界面及字体选取 

如果您的Windows95系统中安装了中文系统,对界面做汉化是极其方便的。例如,您可以将Button1的Caption属性改成“&R矩形”,同样地可改变其它部件的属性,将窗体做成中文的操作界面。

若对中、西文字体不满意,则可以调整Font属性以满足您的要求。Font属性的前面,有一个小小的“+”号,这说明它表征的是集属性,也即属性不再是一个单值,而是一个属性的集合。双击Font,Object Inspector将在下面扩充显示它的其它属性。Color用来表示文本的颜色,Name定义了字体名,如System,MsSerif,Arial,宋体,黑体等。Style下又拥有四个属性:fsBold,fsItalic,fsUnderline和fsTrikeOut。如果想让字体有其中的某种风格,可把相应的属性值设成True。

Font对话框可以更为直接地设定以上属性。Font属性右侧值段有带省略号的按钮,单击它可以激活Font对话框,如图1.7所示:

这一对话框中包含了上述的全部属性,使得调整字形更为直接方便。但要申明的是,对于窗体Form来讲,Font属性改变的是窗体中其它部件的显示字形,它本身的标题字体是缺省的System字体,不能通过属性来改变。

1.2.4.4使用图形编辑对话框 

最后,我们用图标按钮BitBtn1来控制Shape部件的颜色。选中图标按钮,将它的Caption属性改为“&Color”(或“&C颜色”)。可以在按钮中加入一个图标来形象地表述它的功能。选用Object Inspector中的Glyph属性,点动值段的三点按钮,弹出图形编辑对话框。

  您可以通过图形编辑对话框装入图标,在本例程中,按动Load按钮,选择\Delphi 2.0\Images\Buttons\Brush.bmp文件装入,Bitbtn1就成为一个图标按钮。另外,Delphi还为您提供了丰富的Windows标准图标按钮。想运用它们,改变BitBtn的Kind属性。用户不妨尝试一下,选择其它值可以生成漂亮的标准按钮,如OK,Cancel等等。这会使您在以后的程序开发中受益匪浅。

至此,界面的设计工作就已完成了。运行一下观察效果,别忘记保存您的库单元文件、工程文件。 

1.2.5 编写事件处理过程 

  完成用户界面,只是建立了一个“骨架”,下面要做的便是给程序加入“灵魂”,也即,使它能够完成所要求的功能。 

1.2.5.1 为用户操作建立“事件”

单击窗体上的“Rectangle”按钮,在Object Inspector中,点动“Events”页标签,出现事件窗口。在本例程中,我们只关心OnClick事件,即按钮接收到左键单击时应用程序所作出的反应。在Object Inspector窗口中双击OnClick事件右端的值段,会使得Delphi激活库代码编辑器,并将光标停在该按钮所对应的过程的begin...end之间。“Rectangle”按钮的功能是使Shape部件的形状为矩形。联系前文我们查看过的Shape部件的“Shape”属性,现在,我们要做的是在程序中控制部件的属性。

在光标处键入以下的程序段: 

  Shape1.Shape := stRectangle; 

用同样的方法,在Button2和Button3的OnClick事件响应内复制以上语句,并分别将赋值号后的属性值改为stRoundRect和stEllipse。为防止输入错误导致程序运行出错,建议您使Shape1部件的Object Inspector可见,检视属性Shape的各个值并对应输入。

  在代码编辑器中,Delphi自动建立的过程以关键字Procedure开头,用户可以在begin…end之间加入自己的程序,也可以定义变量。这个程序块对用户的外部事件(此例中是按动按钮)进行响应。我们称之为事件处理过程。 

1.2.5.2 使用颜色编辑对话框 

       图标按钮“Color”要控制部件Shape1的颜色,我们可以调入颜色编辑对话框,用以选择要变成的颜色。这时就要用到运行时不可视的部件ColorDialog了。同样地使图标按钮BitBtn1响应OnClick事件,在它的过程中加入以下的语句:

  ColorDialog1.Execute;

  Shape1.Brush.Color := ColorDialog1.Color; 

这样,图标按钮的功能也就完成了。

        初学者可能对这样的程序段不能理解。其实也是十分简单的。查看ColorDialog的Brush属性,就会发现它是个集属性,在其下还有一个Color选项,正是我们想改变的属性。既然我们想将图形的颜色变得更加丰富多彩,它本身提供的几种颜色就已不能满足要求了。用颜色编辑器可以得到更多的色彩。程序的第一句用Execute方法,使得ColorDialog运行它本身。当用户在对话框中进行操作,选定了一种颜色时,即使得TColorDialog对象的一个属性Color置为选定颜色的值。将此值赋给Shape1的Brush属性的子属性Color,就将选定的颜色加到了图形上。

读者的要求可能会是,怎样尽快的了解这些这些方法、属性和域呢?下文对使用联机帮助的简单介绍可能会对您有所裨益。 

1.2.6 使用联机帮助Help 

        事实上,使用联机帮助是您全面地了解可用的方法、对象的域、属性等信息的最快捷途径。您还可以参考Delphi给定的例程。较为简单有效的方法是使用Help中的搜索功能。如果您对一个对象知之甚少,选用主菜单中的Help|Help Topics项,在弹出的帮助对话框,选用Indexes(“索引”)页,Delphi 2.0会提示您输入要检索的主题。以您想用的部件的对象名作为检索主题词,可以参阅很多基本的信息。例如,上文的例程中,在编辑框中键入对象的名称TColorDialog,在词条中就会出现该主题,按动Display按钮显示出TColorDialog的帮助信息。查看method,可以找到能应用在CclorDialog1上的方法Execute;仔细阅读它的功能,上述的编程便不难理解了。Properties中提供了部件的所有属性,在关键属性Key Properties前加上了“金钥匙”,提供了详尽的说明。在Task中您可以查阅该对象的其它相关主题,还可以参阅Example中的应用例程。用帮助窗口的Edit各种功能进行复制、剪贴等操作。

如果您已经对对象有一定的了解,想查阅它的具体方法、域等,可以直接用主题词进行检索。 

以上,我们已经完成了简单的例程。运行您的程序,选用各个按钮改变图形的颜色和形状。如图1.9所示。虽然它只是一个“小玩具”,却直观地给您一个用Delphi编程的印象。对比一下您亲手编制的程序量和程序的功能,您就会理解Delphi为程序员提供了怎样的方便。

1.3 Delphi 2.0的可视化部件用法简介 

本节中,我们将较为深入地讲解Delphi的部件。这是编制大型复杂应用程序的必由之路。下面,我们将对各种部件分类进行简单的介绍,以便于您在编程时能够较快地了解有哪些部件可能会满足您的要求,并对其重要的方法、属性等有一个大致的了解。

  基本技巧、工具的介绍在本章的第4节中,交互阅读这两部分,多创制几个试验程序,有助于您迅速进入Delphi世界,尽早将这一精妙软件用于实际工作中。 

1.3.1 常用的文本相关部件 

        以下的部件以各种格式显示文本,其中有些是文本和数据输入的途径。它们是Label(标签)、Edit(文本编辑)、MaskEdit(格式编辑)、Memo(备注)、List Box(列表框)、Combo Box(组合框)。除了MaskEdit在Additional页之外,其它的都在Standard页中。 

1.3.1.1 Label部件 

        Label(标签)一般放在对象的旁边,用来标记这些对象,从而对用户的操作进行提示,也可以用来显示其它信息。您可以在标签中设置热键,也即在Caption属性值段中输入含有“&”的字串,当用户使用“Alt+关键字母”时,将自动选中它所指向的对象。方法是设置Label部件的FocusControl属性,在值段中,选用与它关联对象的对象名。 

1.3.1.2 Edit、MaskEdit和Memo部件  

        Edit、MaskEdit、Memo部件都是用作接收、显示用户输入文本的。它们具有一些相同的属性。ReadOnly在运行时间内控制对象是否可以进行Windows的操作,当此值为False时,该框内的文本就不能被复制到剪贴板上。MaxLength可以设置输入文本的长度限制。用PasswordChar属性可以按照显示隐蔽密码的方法显示用户输入文本,例如,它的缺省值为“*”,运行时,您的输入将用“*”来显示,从而提供了一种安全措施。您可能也发现了,当一个字段被加上高亮度显示时,按键操作会将这一字段删除,替换成当前的键盘输入。这种设置为操作提供了方便,您不必每次先删除原来的文本;但也可能会导致误删文本。将AutoSelect属性设置成False,这种替代功能就被取消了。

MaskEdit是格式文本输入对象。它的EditMask属性为它提供了过滤文本的格式。点动这一属性的省略按钮,会弹出过滤编辑对话框,除了Delphi为您提供的几种屏蔽格式,您也可以自己编写,查阅“帮助”,会为您提供更详细的用法介绍。

Memo是备注框,与以上对象不同的是,它可以接收多行文本输入。将ScrollBars设置成ssVertical,可以为它加上一个垂直的滚行条。Align属性调整该对象在窗口中的对齐情况,有alNone(无对齐指定)、alBottom(底部对齐)、alClient(全窗口显示)等可以选择;而Alignment属性则决定了文本在框中的对齐显示格式。Lines属性访问的文本被存储在一个TStrings对象中,按动它的省略按钮,可以通过对话框向它增加文本,也可以用程序对这一属性进行操作,以达到修改或增加备注文本的目的。 

1.3.1.3 List Box和Combo Box部件 

List Box(列表框)和Combo Box(组合框)都显示列表项目,所不同的是组合框占用较少的空间。常见的Windows操作系统中,显示可用磁盘驱动器时,用的就是组合框。列表框则提供了一个项目列表以供选择,如Windows打开文件操作时显示文件列表的就是List Box。

  下列程序段将Edit1的文本加入到ListBox1中,并清空Edit1中的文本: 

 procedure TForm1.AddButtonClick(Sender:TObject);

 begin

ListBox1.Items.Add(Edit1.text);

Edit1.Text := ‘’;

 end; 

常见的组合框初始化用以下的语句可以实现,它用下拉式列表框的第一项来初始化组合框的缺省值: 

procedure TForm1.ComboBox1Text(Sender:TObject);

begin

if ComboBox1.Text = ‘’then

ComboBox1.Text := ComboBox1.Items.String[1] ;

   end;

以上就是文本输入的基本部件。 

1.3.2 按钮和检查框部件 

1.3.2.1 Button和BitBtn部件

        按钮是Windows常用的部件,前文的例程中我们已经基本掌握了Button和BitBtn的主要用法。它们一般用在对话框中,做为执行某种功能的指示。您也可以为它们设置内置返回功能,将ModelResult属性加以匹配,可以不用编程,直接实现系统内置的功能,这一点上和Kind属性颇类似。 

1.3.2.2 Speed Button部件 

Speed Button(加速按钮)部件在Additional页上,是进行工具条快速设计的理想部件。它只有一个位图,没有标题。需要提起读者注意的是,用多个Speed Button制作一个工具条时,必须先放置一个窗口类部件,如Panel(操作板)、Group Box(群组框)等。否则试图将加速按钮先制作好再移上去,您会发现它会总是处于窗口类部件下面不可见。当然,您可以对加速按钮进行复制、粘贴到窗口类部件上处理,那就另当别论了。 

1.3.2.3 Check Box与Radio Button部件 

        Check Box(检查框)和Radio Button(无线按钮)部件通常是相提并论的,多用作接收用户“是”或“否”的输入判别。它们一般都是成组的放置在Radio Group或Group Box部件中。所不同的是,成组的Radio Button是“互锁”的,用户选择定一个后,其它的将自动设置为不选;而Check Box部件则是分立的,您可以同时选中其中的几个,也可以一个都不选。 

1.3.3 分组、分界部件 

        分组部件把其它的部件组合成一组,例如上述的无线按钮部件Radio Button,就需要放入以下的成组部件中。分界部件用于在窗口中产生边界和区域,以区别于其它区域。 

1.3.3.1 Group Box、Radio Group及Panel部件 

        Group Box(群组框)、Radio Group(选项按钮组)部件都可以将部件分类、成组。它们都有标题,可以用文字表征成组部件的标题或信息。Radio Group可以进行Column和Item属性的设定,以决定其上的无线按钮的提示文本及显示格式。Panel部件也可以达到将部件分组的目的。通过编程向窗口加一个操作板部件,以书写提示和帮助信息,也不失为一种好方法。以上三个部件都在Standard页。 

1.3.3.2 NoteBook、TabSet及TabbedNoteBook部件 

        记事本部件NoteBook一般和标签集部件TabSet共同使用,用来创制含有标签的重叠多窗体窗口。Pages属性包含了一个页名列表,在编程时,将TabSet的Tabs属性设置成NoteBook的Pages属性,就可以使页标签和相应的窗体对应起来。

        另外一个部件TabbedNoteBook是带有标签的多窗体窗口,不过,它的页标签设在窗口的上面。它们相当于多个分组部件的集合,每次查看其中的一页。以上三个部件在Win 3.1页。

        在Win95页上还有TabControl、PageControl等部件。它们对于生成Windows 95风格的标签集是很重要的。 

1.3.3.3 分界部件 

在Win 3.1页上,还有Header(表头)部件,它在窗口中产生一个凸起的题条,提供了一个可视化的文本显示区域。Additional页的Bevel(立体框)部件提供了一个方框,它的单一线条或整个边框都可以通过Style属性设置为外凸或内凹,可以美化窗口。在Win95页上,HeaderControl、StatusBar等部件,为您使用Winows95风格的界面提供了重要元素。 

1.3.4 图形、图象部件 

1.3.4.1 Image部件

图象部件Image在Additional页上,用来在窗口中显示一幅图片,可以在picture属性中调入图象文件。Delphi支持位图(.BMP)、图标(.ICO)、图元(.WFM)三种文件格式。比较重要的属性是Autosize和Strech。它们决定了图象在窗口中的显示尺寸。Autosize属性为True表示按原尺寸显示,Strech属性为True表示图象按对象图框的大小显示,这时图象的大小可以人为改变。


想死你们了!

TOP

DELPHI基础教程

第一章 Delphi快速入门(四)

1.3.4.2 Shape部件 

   图形部件Shape在前文中我们已有了解,它可以处理多种几何形状,通过设置Pen和Brush的嵌套属性,可以设置图形边框颜色、线型及图形的风格、填充方式、贴图方式等。 

1.3.4.3 PaintBox部件 

   在System页上还有一个PaintBox(绘图框)部件,它在窗体上为您提供一块可供绘图的区域。这一部件需要编程实现它的功能,一个只有在运行时才有效的重要属性Canvas是完成绘图的关键。PaintBox部件不能单独存在于窗体中,必须把它放在固定的分组部件中。 

1.3.5 关系图、文件列表部件 

1.3.5.1 OutLine部件 

   OutLine(略图)部件在Win 3.1页上,它适用于显示分层的数据、文本。在Delphi中,略图部件具有很宽的设置范围。它的Lines属性可以设置每一词条的文本或数据。一般一个空格相当于一个层次,如果在项目前加一个空格则表示它处于树的下一层。所以在进行Lines属性编辑的时候不能用Tab键。在编程时用Lines和Items属性来访问略图的名称、索引及完整的路径名。OutLineStyle属性用来配置显示时关系图的风格,您可以选定用旁边有图标的缩进方式,也可以选择连线方式。 

1.3.5.2 目录访问部件 

    Delphi为您提供了强大的文件目录访问部件。您可以针对具体的用途设计自定义的文件对话框。在部件选项板上选中System页,您将发现这些部件,它们是文件列表框FileListBox、目录列表框DirectoryListBox、驱动器下拉式列表框DriveComboBox和过滤式下拉列表框FilterComboBox部件。

  文件列表框部件FileListBox显示当前目录中的文件,缺省的设置是显示所有文件。通过改变Mask属性来设置过滤器,可以适用DOS的标准通配方式,选择要显示的文件名。当指定多个过滤器时,之间用“;”隔开。

    目录列表框部件DirectoryListBox显示当前驱动器内的目录,并且允许用户在程序执行时切换目录。Column属性决定目录在窗口中用多少列进行显示,当窗体空间不够时,用几列进行显示将有助于全面地显示目录信息。在Delphi中,当窗口空间显示不下全部信息时,将自动加上滚行条,以方便用户的操作。

  驱动器下拉式列表框部件DriveComboBox显示当前所在的驱动器,并且在执行时允许用户在驱动器之间切换。作为程序员在应用这一部件时,应考虑当用户选择到不存在的驱动器时,进行容错、提示处理。

   FilterComboBox(过滤式下拉文件列表框)部件显示当前文件过滤器类型,例如*.*,*.pas等。在执行时允许用户在下拉列表中选择要显示的文件类型。对Filter属性进行初始化,就会得到下拉式列表。

   除了以上的四个部件外,在Sample页上,Delphi还提供了一个目录略图部件DirectoryOutline。它将当前磁盘中的目录结构显示成一个多层次的略图,也允许用户在运行时进行目录的层次显示切换。 

1.3.6 滚动部件 

1.3.6.1 ScrollBar部件 

   ScrollBar(滚行条)是在Windows应用程序中常见的结构,在Delphi中多数部件有自己的是否加入滚行条的属性ScrollBar,但一个独立的滚行条部件仍是很有意义的。它在部件选项板的Standard页,提供一种可以改变菜单或是画面中可见部分的工具,可以以一定的增量在一定范围内滚动。Position属性是个整形值,如果需要以动态的方式显示滚行条,可以在程序中通过操作这一属性来实现。 

1.3.6.2 ScrollBox部件 

    ScrollBox(滚动框)部件是加上了水平、垂直滚行条的群组框部件,它在Additional页上,用作在窗体中提供一个可以多方向滚行的工具。如果您只想显示较大幅面的一个部分,并允许您的用户对其进行滚行操作,ScrollBar将是理想的选择。您可以先在全幅面的滚动框中进行全面设计,然后适当地缩小外框,两个方向的滚行条将自动显示。它的Position属性是集成在HorzScrollBar和VertScrollBar两个集属性下的,编程时,可以选定这两个属性进行操作。 

1.3.6.3 几个进度显示部件 

    在System、Sample、Win 95、OCX等页上,Delphi还为用户提供了几个部件,完成显示进度,增量等操作。部件Gauge可以显示成长条状的或饼状的进度指示仪表。例如在安装软件时,可以编程让这一部件显示目前安装的百分比。

  微调按钮部件SpinButton含有指示向上、向下的两个按钮,它可以用来调节数值,使被控制的值按一定的增量单位,由用户操作递增或递减。

  微调编辑框SpinEdit是微调按钮和编辑框的结合,它在编辑区域显示了数值,又在右侧设了微调按钮,使得用户选择调节按钮或改变数值的方式,来调整数据的值,并返回Value属性。

    其它的此类部件,因篇幅限制,此处不做赘述。具体部件的信息,按照名称在在线帮助中查询其方法、域及应用实例,可以得到详细应用资料。  

1.3.7 网格、表格部件 

     在Additional页中,还有字符串表格部件StringGrid,绘图表格部件DrawGrid等。它们可以用来模拟按网格排列的事物,如成排的按键、操纵钮、字符串等。属性RowCount和ColCount设置了在网格中显示的行列的数目,设置FixedCols和FixedRows的值,可以固定一些行列避免运行时被用户改变。Options集属性含有字符串表格部件的显示方式、操作方式等,可以查阅帮助获得详细的信息,此处不再一一表述。

     图形表格部件DrawGrid使得用户可以用表格的方式显示非文本的数据,它的应用面比字符串表格部件更为广泛,可以把图片和文本一起放在网格中。

颜色网格部件ColorGrid在Sample页上,它提供控制前景色和背景色的界面,通过属性ForeGroundColor和BackGroundColor可以访问到,也可以通过程序,把得到的颜色赋值给其它对象的颜色属性。

  Sample页的日历部件Calendar也是用表格的方式表现的,它提供了一个简单的以月份为库单元的日历表格,通过设定Year和Month的值,可以得到相对应的月份的日历。 

1.3.8 多媒体(MultiMedia)和OLE部件 

  媒体播放器部件MediaPlayer和OLE部件可以在System页上找到。媒体播放器部件在处理多媒体文件时很有用。它显示一个VCR风格的控制面板,让您记录或播放多媒体的图像、声音文件。您可以通过属性的设定来控制部件上的各个按钮,如改变显示颜色、增减按钮数目等。改变一下ColoredButtons、EnabledButtons、VisableButtons的各个子属性值,您就不难发现它们控制的显示效果。具体的应用已经超过本简介的目的,请参阅帮助以及后文的详细介绍。

  OLE客户端部件OLEContainer在窗体中创建一个OLE用户区域,用于与Delphi外部的某个对象进行接口。若对外部的Paradox表格、Word文件、扩展页等进行操作,则对象本身的应用程序必须支持OLE操作。Delphi提供了支持OLE应用的对象清单,单击ObjClass属性的省略按钮,就会显示出来。具体的应用,请查阅帮助或阅读后文的详细阐述。 

  以上,我们介绍了Delphi的基本可视部件。读者可能会发现,在Stardrad页最前面的两个部件MainMenu和PopupMenu还没有提及。因为这是建立菜单的重要、基本操作,我们将在下一节中做详细的介绍。另外,在下文中,我们还将介绍比较重要的不可视部件。至于数据库部件、动态数据交换DDE部件等,因为在后面章节中有详尽的用法介绍,此处不赘述。 

1.4 使用非可视部件 

    上一节介绍的各种部件,在设计和运行时的形式是一样的。本节所介绍的部件,在运行时被隐蔽地嵌入到窗口中,它们本身并不做任何事情,必须被用户的程序所支持才能发生作用。因为部件的设计形状和运行状态并不相关,所以,这种部件在设计窗体中的位置无关紧要,可以放在窗体上不影响其它可视部件的任何位置。这种部件,称之为非可视化部件。

首先,介绍在Standard页上的重要部件主菜单MainMenu和弹出式菜单PopupMenu的详细用法。 

1.4.1 使用菜单部件 

  菜单部件含有内置的交互设计器。在设计菜单时,可以用菜单设计器Menu Designer进行工作。

  应用程序的菜单有两种形式:MainMenu是一般的Windows用户界面必须的部件,它显示在固定的位置,一般用键盘和鼠标左键来激活菜单的功能项;PopupMenu是弹出式菜单,例如,我们前文的小例程在对齐部件时用到的Delphi菜单,就是弹出式的。一般以鼠标的右键或其它快捷键来激活菜单;菜单的位置是在屏幕上“浮动”的,根据鼠标的当前位置决定。

以下,我们将MainMenu和PopupMenu统称为菜单。 

1.4.1.1 进入菜单设计器Menu Designer 

   创建菜单时,首先要在窗体中放置一个菜单对象。然后调入Menu Designer进行菜单的详细设定。用两种方式可以进入Menu Designer:选中菜单部件,点动属性中Items省略按钮;或在菜单部件上双击左键,就会出现Menu Designer。

1.4.1.2 创建菜单的各个项目 

     进入Menu Designer,会发现在菜单的第一项位置上有加亮显示。输入顶层菜单项(实际上是在设定顶层菜单项的Caption属性)并回车。一项菜单条设置完后,加亮显示的位置被移至下拉菜单的下一项,同时,主菜单顶层上会出现虚线框,指示出下一个顶层菜单项的位置。要包含一个隔离条,可以键入短划横“-”即减号并回车。逐步输入菜单的各项,用鼠标左键切换到下一个顶层菜单,直到菜单完成。 

1.4.1.3 设定加速键和热键 

  您可以设定加速键,与前文的例程相同,只需在输入时,将“&”放到需要指定为加速键的字母前面,该字母将被用下划线显示,运行时,按“Alt+加速键字母”可以激活该菜单条。设定热键也是很方便的,只需在Object Inspector中该菜单条的ShortCut属性值段的下拉菜单中,为它选定一个热键组合即可。在运行时,通过“Ctrl+热键字母”来激活菜单条。加速键和热键并不矛盾,您可以同时指定它们。 

1.4.1.4 使用Menu Designer的弹出式菜单 

  Menu Designer中还有一个弹出式菜单,它提供了高频菜单创建命令,并可以对模板选项进行快速访问。在菜单设计器窗口中单击右键,或将光标停在菜单设计器上,按Alt-F10,可以调出Menu Designer的弹出式菜单。使用Menu Designer的弹出式菜单可以向菜单增加菜单项、删除菜单项等操作,还可以对标准主菜单模板进行增加、删除、插入等操作。

  为菜单编制功能,可以双击菜单条,或在特定菜单条的Events页上,双击它响应的事件的值段,Delphi将回到代码编辑器,光标停在事件对应的事件处理过程中,您可以为菜单编制实现任务的代码。

  如果您已经为Speed Bar上的加速按钮编制了事件处理过程,而需要把相应的菜单项连接到事件处理过程上,则只需在该菜单项的Events页的OnClick事件后,输入事件处理过程的名称即可。这样单击菜单项便和按动加速按钮便实现同样的功能。 

1.4.1.5 创建嵌套菜单 

   创建嵌套菜单也是重要的手段。Delphi的嵌套菜单风格是嵌套菜单列在它上层菜单的边上,如图1.11所示: 

   创建嵌套菜单,把加亮条移到它的上层菜单条上,按Ctrl+右行键,将弹出子菜单,就可以按照同上所述的方法进行创建工作了。

    通过拖--放操作,可以实现菜单条的重新定位。用鼠标左键按住某一菜单条,移动到菜单的某一位置,释放左键,菜单条即得到重新定位。菜单设计器允许菜单条在顶层下移动位置,支持菜单条在嵌套菜单中移进、移出,甚至可以将菜单条在各个顶层菜单下属的菜单间移动与定位。

1.4.1.6 给菜单加提示(Hint)

     在菜单中加提示是很好的习惯。它使得您的用户在运行菜单功能前,可以对菜单的任务有一个简略的了解。在菜单条任一项的值段按F1,可以查阅在线帮助,在帮助的正文中找到OnHint并查阅它的例程,将DisplayHint等方法的例程Copy到您的程序中。然后,在菜单条的Hint属性中加入提示正文,运行时当用户将鼠标移到菜单项时,在窗体的底部状态行中会出现Hint属性中的提示信息 

1.4.1.7 菜单的其它属性简介 

   菜单还有其它的一些属性。Enabled属性决定菜单是否被禁用,这在编程中可以实现一定情况下不允许用户使用某项菜单。Checked属性表征了菜单项的开关情况,当Checked为True时,菜单条的前面会出现选中标志。Visible属性控制菜单隐藏与否。设置这一属性可以实现多版本的菜单。被隐藏的菜单项以及它的子菜单都是不可见的,或是不可访问的。

  Popup Menu在用Menu Designer时是完全相同的。只不过它在运行时和主菜单的激活形式有所不同:它是用鼠标的右键激活的。弹出式菜单在一定的窗口范围内激活,所以必须将窗口和弹出式菜单联系起来。在设计完PopupMenu后,须在Name属性中为它命名,然后把这个名称赋给窗体的PopupMenu属性。这样,窗口会接收右键输入,并激活弹出式对话框。 

1.4.2 使用计时器部件Timer 

  在System页上,还有一个(Timer计时器)部件,它能够有规律地触发OnTimer事件,发送信息给应用程序。它是编制应用程序时最为重要的部件之一。 

1.4.2.1 Timer的重要属性 

  Enabled属性表示了计时器打开还是关闭;用Interval属性设置两个OnTimer事件间的间隔,单位是毫秒,也即千分之一秒。将间隔置为0相当于关闭计时器,Interval的最大允许值是32767,也即32.767秒。 

1.4.2.2 使用Timer对程序进行控制 

  计时器是独立的对象,在启动与Windows无关的逻辑和应用事件时极其有用。如:用计时器可以模拟时钟或计时器,可视地显示经过的时间;可以用作系统延时,Delphi提示信息出现只需在该区域停顿几秒,就是Timer应用的一个例子;可以检查系统环境、事件,根据结果进行响应;也可以在窗口中闪烁一段正文或图像,提示某种操作或处理正在进行等等。

  在窗体中放置一个计时器Timer部件,将它的Interval属性置为100(每隔10毫秒触发一次),双击部件进入代码编辑器,在它的事件中加入这样一句程序: 

MessageBeep(0); 

  运行它,会有规律地发出“叮”声。它给您一个应用Timer部件的直观印象。

  尽管Delphi的计时器每秒可以产生1000次激发,在编程中还必须注意程序对计时器激发的响应。如果程序处理OnTimer事件的时间超过Interval的设定值,就可能错过事件,因为当下一次激发到来时,系统正忙于处理上一事件,则这次激发就会被忽略。同时要注意其它的Windows应用程序是否会影响计时器的触发。如果后台正运行着一个占用处理器的程序,就可能会导致Timer的触发不准确,从而使前台程序运行出现错误。 

1.4.3 使用公用对话框部件 

Delphi为您在Dialogs页上提供了几个标准对话框,它们是文件打开OpenDialog;文件保存SaveDialog;字体Fontdialog;颜色ColorDialog;打印PrintDialog;打印设置PrintsetupDialog;查找FindDialog;替换ReplaceDialog等部件。


想死你们了!

TOP

DELPHI基础教程


第一章 Delphi快速入门(五)

1.4.3.1 调用标准对话框 

         您可以发现,除了Font,Find,Replace对话框之外,其余标准对话框的Events页都是空白的。因为它们本身是不可视的对话框形式,所以不能响应标准的鼠标键盘操作,只能在程序中调用它们。在本章第一节的Demo程序中,我们已经对用Color对话框有了了解,在程序的适当位置加入对话框所属的Execute方法,可以执行标准对话框。

         在编制应用程序时,您可以先设计好主菜单。在需要使用标准对话框的菜单项的事件处理过程中,调用标准对话框的Execute等方法,以满足设计需求,即响应菜单命令弹出标准对话框。 

1.4.3.2 OpenDialog和SaveDialog部件 

         OpenDialog(文件的打开)和SaveDialog(保存对话框)部件处理文件的打开与存储,它们拥有完全相同的属性域。DefaultExt属性表示缺省扩展名。当用户没有输入文件的后缀时,可以为用户文件自动加扩展名。FileName属性指定出现在FileName正文框中的缺省文件名。Filter属性提供了文件过滤器。FileEditStyle决定在输入文件名时用Edit还是Combo- Box;设置为fsComboBox时,允许使用历史列表HistoryList属性,这在应用程序需要经常选择文件时,可以节约大量的时间。HistoryList属性保存有在File Name下拉式编辑框中显示的文件名,使用[TStrings]的省略按钮可以编辑历史列表。您也可以编程实现将HistoryList属性设为以前用OpenDialog打开的文件名历史列表。下列程序段可以实现自动历史文件名加载: 

if OpenDialog1.Execute then

OpenDialog1.HistoryList.Insert(0,OpenDialog1.FileName); 

1.4.3.3 ColorDialog和FontDialog 

      颜色对话框ColorDialog我们在前文的例程中已经用过,其更详细的功能如Options的设定请查阅在线帮助。

       字体对话框Font可以帮助用户获取各种方式的字体。使用OnApply事件,可以使得Font对话框中包含一个Apply按钮,按动它,对话框中设定的字体会立即应用到指定的对象中,而对话框仍然是打开的,这样更便于观察修改的结果。

        以下的例程在窗体中先放置一个按钮Button1,调用字体对话框来改变按钮上的Caption属性显示的字体: 

Procedure TForm1.Button1Click(Sender:TObject);

begin

FontDialog1.Execute;

end; 

Procedure TForm1.FontDlgApply(Sender:Tobject);

begin

Button1.Font:= FontDialog1.Font;

end; 

1.4.3.4 PrintDialog和PrintSetupDiaog 

       打印对话框PrintDialog和打印设置对话框PrintSetupDialog可以显示标准打印、打印设置对话框,支持打印文件和打印设置功能,设置它的Options属性可以规定对话框的表现形式 

1.4.3.5 FindDialog和ReplaceDialog 

        FindDialog和ReplaceDialog提供了查找、替换两个对话框部件,对于寻找和替换文本是极其有用的。在FindDialog和ReplaceDialog中都有OnFind事件,当用户单击寻找对话框中的Find Next按钮时将触发这一事件。FindText属性中保存了用户在Find What编辑框中输入的文本。在ReplaceDialog中还有OnReplace事件,当用户单击替换对话框中的Replace和Replace All按钮时,将触发OnReplace事件。FindText和ReplaceText属性分别保存了用户在Find What和Replace With编辑框中输入的文本。 

1.5 使用Delphi的工程管理、设计工具 

       Delphi 2.0是完备的工程设计系统,除提供了大量的可视化编程部件和简单方便的设计方法外,还备有功能强大的工程管理、窗口设计工具。 

1.5.1 创建多窗体工程项目 

       绝大多数的应用程序都用到不只一个窗口,Delphi允许用户创建多文档界面(MDI)应用程序。应用自动创建窗体、窗体样板、对话框专家、应用专家等,可以使得创建应用程序更为方便快捷,而且可重复利用,大大地降低了工作量。 

1.5.1.1 创建一个含有About框的例程 

     作为例子,我们先创建一个含主窗体和简单的About框的应用程序:

     在Delphi中创建一个新工程,并为空窗口增加一个Button部件。我们把它作为主窗体,目的是当按动按钮部件时,调出About窗口。下面我们直接进行About窗口的创制。

     选用主菜单的File|New项,在New页中,选用Form样板,Delphi会自动创建一个空窗体Form2。您可以在其上进行About框的设计,例如加入标签以显示各种信息、加入图片帮助说明等。将Form2的BorderStyle属性设置为bsDialog,则窗口成为运行时不能改变大小的对话框。

     下面编程实现主窗体对About框的显示控制。很可能在设计时,两个窗体出现重叠,Form1被About遮住。用Shift+F12产生View Form对话框,可以选择窗口的名字,调出所需的窗体Form1,双击其上的按钮部件,在事件处理过程加入以下的程序: 

procedure TForm1.Button1Click(Sender: TObject);

begin

Form2.Show;

end; 

        由于Form1窗体调用About窗体,所以必须在Form1的库单元程序中对引用About进行说明。在Unit1的Uses后面加入About的库单元名Unit2。运行程序,在主窗体上按动按钮,可以出现About框。在框外单击鼠标,会回到主窗体中。

        在About窗体中加入一个按钮,并将按钮的ModalResult属性设置为mrOK,同时将按钮的标签的Caption改为OK。这样一个有模式的About框已经建成了。将窗体Form1中Button1的OnClick处理过程的代码改变如下: 

Form2.ShowModal;  

       再运行程序,除了与上述功能相同显示About对话框外,该程序只有当用户按动About框的按钮或被About窗控制图标关闭窗口后,才会回到主窗体中,而不能与第一个窗体发生交互行为。这就是方法Show和ShowModal的主要不同之处。 

1.5.1.2 指定自动创建窗体 

      上面我们简单地生成了一个含About框的双窗体应用程序,运行时,应用程序启动时将自动创建第二个窗体。窗体保持隐蔽状态,但仍然占用Windows的资源。在大型应用程序中,有时不希望在应用程序加载时自动创建所有窗体。Delphi可以指定哪些窗体被自动创建。

       选用Project|Options,Delphi显示Project options对话框。如果Forms不是当前页,按动下标签使之可见。如图1.12所示:

       图示为Delphi装载的TextEdit例程(...\Delphi 2.0\Demos\doc\TextEdit.dpr)。在MainForm正文框中输入主窗体的名字。当应用程序启动时,主窗体自动打开并获取输入焦点。对MDI应用,主窗体的FormStyle属性必须设置为fsMDIForm。在Auto-created Forms列表框中列出了在启动时自动创建的窗体,缺省时工程文件的所有窗体都在此列中。

       如果不需要自动创建窗体,使用箭头按钮把窗体移动到Available Forms列表框中。自动创建的窗体可以用Show方法进行显示,而不自动创建时,必须编程实现窗体的显示。在TextEdit工程中,定义一个TEditForm类型的变量EditForm,使用了以下的代码显示第二个窗体:

Begin

EditForm := TeditForm.Create(Self);

EditForm.Open(OpenFileDialog.Filename);

EditForm.Visible := True;

End;

也即,必须使用Create方法创建窗体,同时将窗体的Visible属性设为真。 

1.5.2 使用工程管理器Project Manager 

        当窗体的数目逐步增加时,跟踪窗体以及与之相连的库单元代码就变得比较烦琐。用Delphi提供的工程管理器Project Manager可以使得管理各种窗口更为方便。

1.5.2.1 工程文件的组成 

        工程(project)是整个应用程序的源文件集合,这样的文件一共有三种:

        1.一个包含主程序部分的工程文件,用以驱动由Delphi创建的Windows程序,扩展名为.DPR。这是在用户的工程被装载时就运行的全局应用程序文件,它的名字出现在Project Manager的标题条上。工程文件一般是Delphi自动创建的,用View|Project Source可以调出该工程的源文件,如果需要,可以对工程源文件进行编辑。

        2.一个或多个窗体文件,它们含有相应的库单元文件。窗体文件的扩展名是.DFM。这些文件含装在窗体上的控制部件。在Delphi中这样的文件是在用户设计界面的时候由Delphi自动生成的,而且不能编辑。在Delphi中,一个应用程序至少需要一个窗体。

         3. Object Pascal库库单元文件。 它们的扩展名为.PAS。窗体的库单元文件包含了程序部分,用以控制窗体的行为。在一个工程中,还要包含附加库和被应用程序其它部分调用的支持子程序。uses子句中包含的标准库单元文件包含了支持可视窗体及其控制的对象声明。这些库单元也包含了用于激活窗体的事件处理过程程序。 

1.5.2.2 使用Project Manager进行工程管理 

        选用View|Project Manager,会调出Project Manager窗口。

        Project Manager主体部分列出了库单元以及相关联的窗体的路径,当库单元和工程在同一目录下时,路径不再显示。Project Manager包含了应用程序的所有源文件和窗口。并不是每一个库单元都拥有一个相关联的窗体。Project Manager可以作为工程的一个简捷目录。在列出的任何一个窗体或库单元上双击鼠标左键,Delphi就会产生此对象的代码编辑器。

        Project Manager 窗口上有增加、删除对象以及查看对象的加速按钮。Options加速键能够打开前文所述的Project Options对话框。如果在Project Manager打开时编辑了工程各文件的源代码,可单击Update加速条按钮刷新对象列表。 

1.5.3 使用窗体样板和对话框专家 

        在前文的例程中,我们创建了一个简单的About框。在很多应用程序中,都会用到这种进行信息提示或表示版本信息的对话框。Delphi的设计思想是软件可重用,所以系统phi已经载入了一些用户可重复应用的窗体样板。使用窗体样板,可以简单地生成各种对话框,节省了大量的重复工作。 

1.5.3.1 使用窗体样板创建窗体 

        创建一个新工程时,Delphi为用户创建了一个新的空窗体。如果需要使用一个窗体样板,向工程中增加一个窗体。选用File|New,Delphi即会显示New Items对话框。在Forms页上,从样板库中选择中意的新窗体增加到工程中。例如,Delphi提供了About Box样板。如果选择它,About对话框就增加到工程中,用户只需加入自己的各种信息即可,这样就简化了工作。同样,要建立一个对话框窗口,在Dialogs页上,选用所需的对话框样板,可以简便地建立起所需的对话框窗口。

1.5.3.2 存储自己的窗体样板 

        您可以在样板库中存储自己的窗体样板。首先创制一个窗体的样本,在窗体区域中单击鼠标的右键,可以调出窗体的加速菜单。选用Add To Repository...,将弹出对话框。

       在Forms列表框中,选择想要创建为样板的窗体。在Title输入框中输入想要在样本库内图标下出现的文本。在Description区,输入关于样板的详细说明。这样在浏览样本库时,在状态行上会显示这段文字以进行提示。可以选择一个图标作为新窗体样板的图标,按动Browse按钮,选择一个现有图标来代表新的窗体样板。否则将使用按钮左方的缺省窗体图标。按OK按钮,Delphi会要求您确认将窗体存储为样板。

       再次打开New Items对话框,您会发现您的窗体已经被存储成一个窗体样板。 

1.5.3.3 使用对话框专家Dialog Expert 

       对话框样板呈现了友好界面的被保存对话框,而对话框专家却能根据用户提供的信息,智能地创建复杂的对话框。

       选用File|New...,在New|Items的Dialog页选用Dialogs Expert,Delphi将提示回答各种问题,以便于建立复杂的对话框。

1.5.4 使用工程样板和应用专家 

        Delphi的工程样板提供预先设计的工程来作为应用程序开发的起点。而应用专家则允许用户按照一定的模式来创建一个应用程序。 

1.5.4.1 使用工程样板 

        当您通过一个工程样板来开始一个工程时,除了空白工程样板外,都会被要求指定一个唯一的子目录即工程目录Project Directory,用来存储新的工程。如果您指定一个不存在的目录,Delphi会帮助您生成它。您可以通过加入新的窗体和程序库单元来修改它,或不加修改地直接利用,而只是加入您的事件处理过程。无论您如何修改,您所作的修改只影响到打开的工程,原先的工程样板不会受到影响,可再次被利用。

         选用File|New,在New Items对话框中选用Projects页,可以选择将MDI、SDI、Windows 95 Logo等应用程序作为缺省的工程类型,点动所需工程的图标,使之高亮度显示,然后按OK按钮。如图1.17所示。在接下来的Select Dictionary对话框中指定一个用来包含新的工程文件的目录,则一个工程样板的副本在指定的目录中打开。

1.5.4.2 使用应用专家Application Expert 

        一个应用专家就是一个应用程序,它会依照您在一系列对话框中所设置的选项值来产生一个工程。

        如果您想在打开一个工程时,利用应用专家来取代工程样板而成为缺省值,则使用File|New,在New Items的Projects页上选用Applcation Expert。这样,双击它的图标,会弹出一系列对话框提问,要求您选择您的工程是否需要一个标准的Windows 95对话框、含有哪些菜单、加速条等。图1.18是使用应用专家后出现的第一个对话框。

Delphi 会要求您输入您的工程文件名及它存储的唯一目录,并提示您选择应用程序是否使用MDI窗口、是否含有状态行、提示等。选项设置完毕,按动Finish按钮,Application Expert即按照您的要求,建立起一个可以直接运行的工程文件。


想死你们了!

TOP

DELPHI基础教程

第二章 Delphi面向对象的编程方法(一)
        Delphi的编程语言是以Pascal为基础的。Pascal语言具有可读性好、编写容易的特点,这使得它很适合作为基础的开发语言。同时,使用编译器创建的应用程序只生成单个可执行文件(.EXE),正是这种结合,使得Pascal成为Delphi这种先进开发环境的编程语言。

        本章中,我们将讨论Object Pascal的主要特点,并讲解如何在事件处理过程和其他应用程序中,使用它来编制程序代码。本章将讲解Delphi应用程序中最常用的Object Pascal语法,而不是Pascal语言的一切细节。如果您完全不熟悉Pascal编程,请参阅一些基础的Pascal教程。如果您具有编程经验,并能熟练地使用其他流行程序语言,您将在本章的Object Pascal中发现一些相同的概念。如果您已经熟悉了Borland Pascal,就可以快速浏览或跳过本章。 

2.1 编写Object Pascal程序代码 

        在前边的章节中,我们通过例程,已经编写了几行简单的代码。在本章中,我们将从熟悉Pascal编程的角度,配合实例,讲解Object Pascal编程的基本方法。

        在编写自己的Object Pascal程序时,要注意程序的可读性。Pascal语言是英式结构语言,在程序中选择合适的缩排、大小写风格,并在需要时将程序代码分行,会使得程序代码能够很容易地被自己和他人读懂。一般的程序员都有这样的体验:如果不给程序加上适当的注解,一段时间后,自己也难以理清程序的流程。给程序及时地加上注释是良好的编程习惯。Delphi的注释需要加注在{}之间,编辑器会把它们处理成为空白。Delphi保留了Borland Pascal编辑器的风格,关键字采用黑体字,被注释的部分会变暗,这使得编程风格良好,易读易写。 

2.1.1 编写赋值语句 

        在事件处理过程中,最常用到的工作就是把一个新值赋给一个属性或变量。在设计用户界面时,可以使用Object Inspector(Object Inspector)来改变其属性;但有时需要在程序执行时改变属性的值,而且有些属性只能在执行时改变,这些属性在Delphi的在线帮助的“Proprety”主题中被标为执行期属性。进行这种改变,就必须使用赋值语句。

       下文的赋值语句表征一个OnClick事件。当按钮按动后,将编辑框部件Edit1的Color属性置为clRed:

procedure TForm1.Button1Click(Sender: TObject);

begin

Edit1.Color := clRed;

end; 

        当按动按钮后赋值语句被执行,编辑框变成红色。

        在语句中,部件的名称在属性前,中间用“.”表示属性的所属关系。这样就准确地指定了要将clRed值赋给哪一部件的哪一属性。赋值号为“:=”,不论给属性还是给变量赋值,都是将右边的值赋给左边的属性或变量。

         当将一个属性值、变量、常量或文本数据赋给属性或变量时,所赋值的类型和接受此值的属性或变量的类型应相同或兼容。一个属性或变量的类型定义了此属性或变量的可能值集合,也定义了程序代码可以执行的运算。在前边的例程中,编辑框部件的Color属性和clRed的类型都是TColor。可以在在线帮助中找到一个属性的类型;另外一种方法是在Object Inspector中选定该属性值段,并按下F1键,则类型将在属性说明的结尾处列出,例如Color属性列出下边的语句: 

Property Color : TColor; 

         有些属性是只读(Read Only)的,它们只能被读取,不能被改变。请查阅在线帮助,在Delphi中这些只读属性都有注解。 

2.1.2 标识符的说明与使用 

         标识符是Delphi应用程序中一些量的名称,这些量包括变量(var)、常量(const)、类型(type)、过程(procedure)、方法(Method)及其他,Object Pascal 在应用标识符时,必须首先说明它们。Object Pascal是强类型语言,它的编译器可以检查确保赋给变量或属性的值是正确的类型,以便于您改正错误。因为Object Pascal是编译语言,所以Delphi的执行速度要比使用解释语言快得多。在使用标识符前说明它们,可以减少程序错误并增加代码的效率。 

2.1.2.1 变量

         变量是程序代码中代表一个内存地址的标识符,而此地址的内存内容在程序代码执行时可以被改变。在使用变量前必须对它进行说明,即对它进行命名,并说明它的类型。在所有变量说明以前加上保留字var。变量说明左边是变量的名称,右边则是该变量的类型,中间用(:)隔开。 

var

Value ,Sum : Integer;

Line : String; 

        在窗体中加入一个名称为Edit1的编辑框,再加入一个名称(属性Name)为Add的按钮部件,并建立如下的事件处理过程:

procedure TForm1.addClick(Sender: TObject);

var

X , Y: Integer;

begin

X := 100;

Y := 20;

Edit1.Text := IntToStr(X + Y);

end; 

        在本例中,当按动ADD按钮时,编辑框中显示值120。在Object Pascal中,必须确保变量或属性被赋予类型相同或兼容的值。您可以尝试将赋给X的值改为100.0,或去掉IntToStr函数,在编译时会出现类型不匹配的错误,这也说明了Object Pascal强类型语言的特点。 

2.1.2.2 预定义类型 

        Object Pascal有多个预定义的数据类型,您可以说明任何这些类型的变量:

        整形:Integer的范围是-32768到32767,占2字节的内存;Shortint从-128到127,占1字节内存;Longint从-2147443648到2147483647 占4字节内存;Byte从0到255,占1字节;Word从0到65535,占2字节内存。它们都是没有小数部分的数字。

        实型:Single可以包含7到8位有效小数部分,占用4字节的内存;Double类可以包含15到16位有效小数部分,占用8字节的内存;Extended类型包含19到20位有效小数部分,占用10字节内存;Comp可以包含19到20位有效小数部分,占用8字节内存。以上实数类型只有在8087/80287选项[N+]打开才可以使用。Real可以包含11到12位有效小数部分,占用6字节内存。它只有在和以前Borland Pascal兼容的情况下才使用,否则应使用Double或Extended。

       布尔型:Boolean,只包含true或False两个值,占用1字节内存。

       字符型:Char,一个ASCII字符;字符串类型String一串最长可达255个ASCII字符。

       指针型:Pointer,可以指向任何特定类型。

       字符串型:PChar,是一个指向以零结尾的字符串的指针。

        除了预定义类型外,Delphi还有自行定义的类型。上述例程的TColor就是这种类型。此外,用户还可以定义自己的数据类型,这部分内容将在下文中详细讲述。

        整型类别和实型类别都各有五种类型,同一类别中,所有的类型与其他同类别的都相容,您可以将一种类型的值赋给相同类别中不同类型的变量或属性,而只需要这个值的范围在被赋值的变量或属性的可能值范围内。例如,对于一个Shortint型的变量,可以接受在-128到127范围内的任意整数,例如Shortint类型的7;您不能将300赋给它,因为300已经超出了Shortint的范围了。将范围检查功能打开(选用Options|Project,并在Compiler Options Page中选择Range Checking),将会检查出一个范围错误;如果Range Checking没有被打开,那么程序代码将可以执行,但被赋值的值将不是您期望的值。

        在一些情况下,您可以进行不同类型的变量或属性的赋值。一般来说,可以将一个较小范围的值赋给一个较大范围的值。例如,您可以将整型值10赋给一个接受实型值的Double属性而使得值成为10.0,但如果将一个Double类型的值赋给整形变量,则会出现类型错误。如果您不清楚类型的兼容性,可以参阅Delphi的在线帮助中“Type Compatibility and Assignment Compatibility”主题。 

2.1.2.3 常量 

       常量在说明时就被赋予了一个值,在程序执行过程中是不可改变的。下面的例子说明了三个常量: 

const

Pi = 3.14159;

Answer = 342;

ProductName = "Delphi"; 

        象变量一样,常量也有类型。不同的是,常量假设其类型就是常量说明中其所代表的值的类型。上文的三个常量的类型分别是real型、整形、字符串型。常量用“= " 表示两边的值是相等的。 

2.1.3 过程与函数 

        过程与函数是程序中执行特定工作的模块化部分。Delphi的运行库包含许多过程与函数以供您的应用程序调用。您不必了解过程与函数的逻辑,但要知道过程与函数的用途。在对象中说明的过程和函数称为方法(Method)。所有的事件处理过程都是过程,以保留字procedure开头。每一个事件处理过程只包含了当这一事件发生时需要执行的程序代码。在事件处理过程中使用Delphi已经存在的过程与函数,只需在程序代码中调用它们即可。 

2.1.3.1 一个调用Delphi方法的简单例程 

      下文将通过对一个Memo部件的文本进行剪切、拷贝、粘贴、清除等编辑的应用程序编制,介绍使用Delphi过程和函数的调用方法。

       Memo(备注)部件有一个CutToClipboard方法,实现将用户在memo中选择的文本移到剪贴板上去。由于这个功能已经被建立在此方法中了,所以您只需知道这个方法做什么以及如何使用它即可。

       下面的语句表明如何调用一个名为Memo1的memo部件的CutToClipboard方法: 

       Memo1.CutToClipboard; 

       通过指定Memo1的名称,说明调用哪一个部件的CutToClipboard方法。如果不指明对象名称,Delphi会显示Unknown identifier错误。当该事件处理过程被触发,程序会执行CutToclipboard中的语句,将Memo1中的文本剪贴到剪贴板上去。

       下文的例程展示了如何调用Delphi的方法,实现将备注部件的文本信息剪切、拷贝到剪贴板上;将剪贴板上的标记文本粘贴到备注中,清除备注部件中的全部文本等四个功能。

       打开一个新的空窗体,加入一个memo部件和四个按钮,并排列整齐。改变按钮部件的Name属性,分别命名为Cut,Copy,Paste,Clear。您会发现,当Name属性发生改变时,Caption属性将发生相应的变化。在Caption属性前加标“&”号设立加速键

        将memo部件的ScrollBars属性设为ScVertical,以便加上滚行条。将WordWrap属性设置为True,这样当用户输入文本到达Memo部件的右边缘时会自动回行。将Line属性第一行的Memo1文本删除,使得memo部件在初始显示时为空的。

为每一个按钮建立如下的事件处理过程: 

procedure TForm1.CutClick(Sender: TObject);

begin

Memo1.CutToClipboard;

end; 

procedure TForm1.CopyClick(Sender: TObject);

begin

Memo1.CopyToClipboard;

end; 

procedure TForm1.PasteClick(Sender: TObject);

begin

Memo1.PasteFromClipboard;

end; 

procedure TForm1.ClearClick(Sender: TObject);

begin

Memo1.clear;

end; 

        执行此程序。您可以在备注部件中输入文本,在进行了文本的标记后,可以任意地进行剪切、拷贝、粘贴和清除。当按钮被按动时,就调用相应的过程进行处理。用户可以通过查阅在线帮助进行Memo部件的Topic Search,在Memo Component项中查阅Method,会得到以上过程的详细说明。 

2.1.3.2 调用Delphi的含参过程 

        有些过程要求用户指明参数。被调用的过程会在执行时使用传入的参数值,这些值在过程中被认为是已经被说明的变量。例如,LoadFromFile方法在TString对象中被说明为: 

Procedure LoadFromFile(const FileName: String); 

        在调用这一过程时,应指明FileName参数是要装入的文件名称。下面的程序将先打开Open对话框,当您选择了一个文件后,Delphi将把该文件读入一个Memo部件: 

begin

OpenDialog.Execute;

Memo1.lines.LoadFromFile(OpenDialog.FileName);

end; 

2.1.3.3 使用Delphi函数 

        与过程一样,函数的程序代码也执行特定的工作。它和过程的差别为:函数执行时会返回一个值,而过程则没有返回值。函数可以用来赋给一个属性或变量;也可以使用返回值来决定程序的流程。

        前文中我们实际上已经接触过了函数。在讲述变量时,曾用到过下面的程序段: Edit1.Text := IntToStr(X + Y);其中,IntToStr(Value)把一个LongInt类型的数值转化为字符串的值,Value是IntToStr唯一的参数,它可以是一个整形的值、变量、属性或产生整形值的表达式。调用函数,必须把返回值赋给和此返回值类型兼容的变量或属性。

        有些函数返回一个True或False的布尔量,用户的程序可以根据返回值来决定跳转。下文的例程讲述了函数返回值为Boolean的判断用法:

        在窗体中加入一个ColorDialog对象和一个Name属性为ChangeColor的按钮。为按钮的OnClick事件建立事件处理过程如下: 

procedure TForm1.ChangeColorClick(Sender: TObject);

begin

if ColorDialog1.Execute then

Form1.Color := ColorDialog1.Color

else

Form1.Color := clRed;

end; 

        此事件处理过程使用一个返回Boolean值的Execute方法。按动按钮,并在颜色对话框中选择一个颜色。如果按动OK按钮,ColorDialog.Execute方法将返回True,则Form1.Color将被赋值为ColorDialog1.Color,窗体显现您选用的颜色;如果按动颜色对话框的Cancel按钮,方法将返回False值,窗体将变为红色。


想死你们了!

TOP

DELPHI基础教程

第二章 Delphi面向对象的编程方法(二)


--------------------------------------------------------------------------------

2.1.4 跳转语句 

Object Pascal的跳转语句有if和case两个。 

2.1.4.1 if语句 

        if语句会计算一个表达式,并根据计算结果决定程序流程。在上文的例程中,根据ColorDialog.Execute的返回值,决定窗体的背景颜色。if保留字后跟随一个生成Boolean值True或False的表达式。一般用“=”作为关系运算符,比较产生一个布尔型值。当表达式为True时,执行then后的语句。否则执行else后的代码,if语句也可以不含else部分,表达式为False时自动跳到下一行程序。

        if语句可以嵌套,当使用复合语句表达时,复合语句前后需加上begin…end。else保留字前不能加“;”,而且,编译器会将else语句视为属于最靠近的if语句。必要时,须使用begin…end保留字来强迫else部分属于某一级的if语句。 

2.1.4.2 case语句 

         case语句适用于被判断的变量或属性是整形、字符型、枚举型或子界型时(LongInt除外)。用case语句进行逻辑跳转比编写复杂的if语句容易阅读,而且程序代码整形较快。

下面的例程显示一个使用case语句的窗体:

建立如下的事件处理过程: 

procedure TForm1.Button1Click(Sender: TObject);

var

Number : Integer;

begin

Number := StrToInt(Edit1.Text);

case Number of

1,3,5,7,9: Label2.Caption := '奇数';

0,2,4,6,8: Label2.Caption := '偶数';

10..100:

begin

Label2.Caption := '在10到100之间';

Form1.Color := clBlue;

end;

else

Label2.Caption := '大于100或为负数';

end;

end; 

        执行程序,当Edit1部件接受到一个值,并按动“OK”按钮触发程序后,Number便被赋值为用户输入的数值。case语句根据Number的值判断该执行哪一条语句。象if语句一样。case语句也有可选择的else部分。case语句以end结尾。 

2.1.5 循环语句 

       Object Pascal的循环语句有三种:repeat、while和for语句。 

2.1.5.1 repeat语句 

        repeat语句会重复执行一行或一段语句直到某一状态为真。语句以repeat开始,以until结束,其后跟随被判断的布尔表达式。参阅以下的例程: 

i := 0;

repeat

i := i+1;

Writen(i);

until i=10; 

       当此语句被执行时,窗体的下方会出现1到10的数字。布尔表达式 i=10 (注意,与其他语言不同的是,“=”是关系运算符,而不能进行赋值操作)直到repeat..until程序段的结尾才会被计算,这意味着repeat语句至少会被执行一次。 

2.1.5.2 while语句 

         while语句和repeat语句的不同之处是,它的布尔表达式在循环的开头进行判断。while保留字后面必须跟一个布尔表达式。如果该表达式的结果为真,循环被执行,否则会退出循环,执行while语句后面的程序。

         下面的例程达到和上面的repeat例程达到同样的效果: 

i := 0;

while i<10 do

begin

i := i+1;

writeln(i);

end;  

2.1.5.3 for语句 

         for语句的程序代码会执行一定的次数。它需要一个循环变量来控制循环次数。您需要说明一个变量,它的类型可以是整形、布尔型、字符型、枚举型或子界型。

下面的程序段会显示1到5的数字,i为控制变量: 

var

i : integer;

for i := 1 to 5 do

writeln(i); 

          以上介绍了三种循环语句。如果您知道循环要执行多少次的话,可以使用for语句。for循环执行速度快,效率比较高。如果您不知道循环要执行多少次,但至少会执行一次的话,选用repeat..until语句比较合适;当您认为程序可能一次都不执行的话,最好选用while..do语句。 

2.1.6 程序模块

        程序模块在Object Pascal中是很重要的概念。它们提供了应用程序的结构,决定了变量、属性值的范围及程序执行的过程。它由两个部分组成:可选择的说明部分和语句部分。如果有说明部分,则必在语句部分之前。说明部分包括变量说明、常量说明、类型说明、标号说明、程序,函数,方法的说明等。语句部分叙述了可执行的逻辑行动。

        在Delphi中,最常见的程序模块便是事件处理过程中的程序模块。下面的事件处理过程是含有变量说明部分的程序模块: 

procedure TForm.Button1Click(Sender Tobject);

var {程序模块的说明部分}

Name : string;

begin {程序模块的语句部分}

Name := Edit1.Text;

Edit2.Text := 'Welcome to Delphi'+Name;

end; {程序模块结束} 

        库单元也是程序模块。库单元的interface部分含有库函数、类型、私有,公有域的说明,也可以含有常量、变量的说明。这一部分可以作为程序模块的说明部分。在库单元的implementation部分中通常含有各种事件处理过程,它们可以视为模块的语句部分,是事件处理模块。库单元模块结束于库单元结束的end.处。

        程序模块中可以包含其他的程序模块。上文库单元模块中含有事件处理模块。而库单元模块实际是在工程程序模块中。

         所有的Delphi应用程序都有相同的基本结构。当程序逐渐复杂时,在程序中加入模块即可。例如在库单元模块中加入事件处理模块,向工程中加入库单元模块等。模块化编程使得程序结构良好,并且对数据具有保护作用。 

2.1.7 关于作用范围 

2.1.7.1 标识符的作用范围 

        一个变量、常量、方法、类型或其他标识符的范围定义了这个标识符的活动区域。对于说明这个标识符的最小程序模块而言,此标识符是局部的。当您的应用程序在说明一个标识符的程序模块外执行时,该标识符就不在此范围内。这意味着此时执行的程序无法访问这个标识符,只有当程序再度进入说明这个标识符的程序模块时,才可以访问它。

         下面的示意图表示一个含有两个库单元的工程,每个库单元中又各有三个过程或事件处理过程。

2.1.7.2 访问其他程序模块中的说明 

        您可以在当前的程序模块中访问其他程序模块中的说明。例如您在库单元中编写一个事件处理过程来计算利率,则其他的库单元可以访问这个事件处理过程。要访问不在当前库单元中的说明,应在这个说明之前加上其他应用程序的名称和一个点号(.)。例如,在库单元Unit1中有事件处理过程CalculateInterest过程,现在您想在库单元Unit2中调用这一过程,则可以在Unit2的uses子句中加入Unit1,并使用下面的说明: 

Unit1.CalculateInterest(PrincipalInterestRate : Double); 

        应用程序的代码不能在一个模块外访问它说明的变量。事实上,当程序执行跳出一个模块后,这些变量就不存在于内存中了。这一点对于任何标识符都是一样的,不管事件处理过程、过程、函数还是方法,都具有这一性质。这样的标识符称为局部变量。 

2.1.7.3 按照作用范围说明标识符

        您可以在应用程序的不同地方说明一个标识符,而只需保证它们的有效范围不同即可。编译器会自动访问最靠近当前范围的标识符。

       库单元的全局变量一般可以说明在保留字implementation后面。例如,下面的例程实现将两个编辑框中的整数相加,显示在第三个编辑框中。用到了一个整形的全局变量Count: 

…implememntation 

var

Count : Integer; 

procedure TForm1.AddClick(Sender:TObject);

var

FirstNumber,SecondNumber:Integer;

begin

Count := Count + 1;

Counter.Text := IntToStr(Count);

FirstNumber := StrToInt(Edit1.Text);

SecondNumber := StrToInt(Edit2.Text);

Edit3.Text := IntToStr(FirstNumber+SecondNumber);

end;

… 

        为了实现每按动一次按钮Count增加一次,必须对全程变量Count进行初始化处理。在程序库单元的结尾处,最后一个end.保留字之前,加入保留字initialization和初始化Count的代码: 



initialization

Count := 0; 

        这样当事件处理过程AddClick被触发时,Count就会被增加一次,以表征计算次数。如果用面向对象编程,则Count可以说明成窗体的一个域,这在下一节中将有讲述。 

2.1.8 编写一个过程或函数

         在您开发Delphi应用程序时,所需的大部分代码都编写在事件处理过程中,但有时仍然需要编写不是事件处理过程的函数或过程。例如,您可以把在多个事件处理过程中用得到语句编写成过程,然后任何事件处理过程、过程、函数都可以象调用已经存在的过程或函数一样直接调用它。好处是您只需编写一次代码,而且程序代码会比较清楚。

2.1.8.1 一个自行编写的函数例程 

        在上文两个数相加的程序中,如果编辑框中无值,则会使得程序出错中断。为避免这种情况,编写下面的函数,检查编辑框中是否有值,如无值,则提醒用户输入: 

function NoValue(AnEditBox:TEdit):Boolean;

begin

if AnEditBox.Text='' then

begin

AnEditBox.Color := clRed;

AnEditBox.Text := '请输入整数值';

Result := True;

end

else

begin

AnEditBox.Color := clWindow;

Result := False;

end;

end; 

        NoValue函数会检查编辑框是否为空,如果是,编辑框颜色变红,并提醒用户输入一个整数,然后函数返回真值;Result保留字在Delphi中用来专指函数返回值。在上文的例程中加入NoValue函数: 

procedure TForm1.AddClick(Sender: TObject);

var

FirstNumber,SecondNumber : Integer;

begin

if NoValue(Edit1)or NoValue(Edit2) then

exit;

Count := Count + 1;

Counter.Text := IntToStr(Count);

FirstNumber := StrToInt(Edit1.Text);

SecondNumber := StrToInt(Edit2.Text);

Edit3.Text := IntToStr(FirstNumber+SecondNumber);

end; 

        如果其中的任何一个返回真值,则表示有编辑框空,会执行exit过程,使得当前的程序模块停止执行,并使得编辑框出现输值提示。当新值被输入后,再执行程序时,红色提示被隐去,恢复正常的计算状态。 

2.1.8.2 过程和函数的标题 

        每一个过程或函数都以标题开始,其中包括过程或函数的名称和它使用的参数。过程以保留字procedure开始,函数以保留字function开始。参数位于括号里面,每一个参数以分号分隔。例如: 

procedure validateDate(Day : Integer; month : Integer; Year : Integer);

      您也可以将相同类型的参数组合在一起,则上述过程头写作: 

procedure ValidateDate(Day, Month, Year : Integer); 

       函数在标题中还多了一项:返回值的类型。下面是一个返回值为Double型的函数标题: 

function CalculateInterest(principal,InterestRate:Double):Double; 

2.1.8.3 函数和过程中的类型说明 

       一个过程或函数程序模块也含有说明部分和语句部分。说明部分可以包括类型说明、变量说明、常量说明等。除了Object Pascal语言中已经定义的类型之外,Delphi的应用程序还可以建立新的数据类型。类型说明部分有保留字type开始。下面是一些类型的说明: 

type

Tcount = Integer;

TPrimaryColor = (Red,Yellow,Blue);

TTestIndex = 1..100;

TTextValue = -99..99;

TTestList = array [TTestIndex] of TTestValue;

TCharVal = Ord('A')..Ord('Z') ;

Today = (Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,

Sunday) ; 

         在类型标识符后面,用“=”号定义了新的类型。类型界定了变量的取值范围,例如,TCount类型的变量必须是整形值;一个TPrimaryColor类型的变量只能是red、yellow或blue等等。每一个类型的名称都是由字母T开始,这并非必须的,但它是Delphi的惯例,在区别类型名和标识符时非常有用。类型说明可以是局部的,也可以是全局的。如果您把它放在implementation后面,则表明对于库单元来讲,它是全局的,所有的事件处理过程和其他的过程、函数都可以调用它。如果类型是在过程中被说明的,则是局部的,离开这一过程,该类型将失效。

         一般来讲,在过程和函数中,任何类型说明都在变量说明之前,而任何变量说明都在常量之前。但是,只要遵从说明必须在过程与函数的标题之后,而且在程序代码之前,即是有效的。


想死你们了!

TOP

DELPHI基础教程

第二章 Delphi面向对象的编程方法(三)

2.1.8.4 过程和函数的语句部分 

        过程或函数的语句部分由begin开始,end结束。函数需要一个返回值。可以将返回值赋给函数名称,也可以将返回值赋给Result变量。下面的例程将返回值赋给函数名称: 

function CalculateInterest(Principal,InterestRate: Double):Double;

begin

CalculateInterest := Principal * InterestRate;

end; 

        将返回值赋给Result变量也是可以的,则上面的程序改为: 

Result := Principal*InterestRate;

下面是这个函数的调用方法:

InterestEarned :=CalculateInterest(2000,0.012);

         在Implementation后面的过程和函数,可以且只能被此库单元的事件处理过程使用。要让过程和函数可以被其他的程序库单元使用,则需要将过程或函数的标题部分放在库单元中的interface部分,而把含标题的整个过程或函数放在库单元的inplementation部分,并在要访问这个过程或函数的库单元的uses子句中加入说明这个过程或函数的库单元名称。 

2.1.8.5 函数的递归调用 

       在Object Pascal中,过程或函数必须先说明再调用。上文的NoValue函数必须在使用它的事件处理过程之前说明和执行,否则程序会报告一个未知标识符的错误。

         以上规则在递归调用时是例外情况。所谓递归调用,是指函数A调用函数B,而函数B又调用函数A的情况。在递归调用中,函数要进行前置,即在函数或过程的标题部分最后加上保留字forword。下文的例程是一个递归调用的典型例子: 



implementation

var

alpha:Integer;

procedure Test2(var A:Integer):forword;

{Test2被说明为前置过程}

procedure Test1(var A:Integer);

begin

A :=A-1;

if A>0 then

test2(A); {经前置说明,调用未执行的过程Test2}

writeln(A);

end;

procedure Test2(var A:Integer);{经前置说明的Test2的执行部分}

begin

A :=A div 2;

if A>0 rhen

test1(A); {在Test2中调用已执行的过程Test1}

end; 

procedure TForm1.Button1Click(Sender:TObject);

begin

Alpha := 15; {给Alpha赋初值}

Test1(Alpha); { 第一次调用Test1,递归开始}

end; 

           按钮的OnClick事件处理过程给Alpha赋初值,并实现先减1再除2的循环递归调用,直到Alpha小于0为止。 

2.1.8.6 过程和函数的参数 

        当您的程序代码在调用一个过程或函数时,通常用参数传递数据到被调用的过程或函数中。最常用的参数有数值参数、变量参数和常量参数三种。

         由被调用过程或函数定义的参数为形参,而由调用过程或函数指明的参数叫实参。在NoValue函数中,说明函数体中的AnEditBox是形参,而调用时在if NoValue(Edit1)…中,Edit1是实参。

         数值参数在运行过程中只改变其形参的值,不改变其实参的值,即参数的值不能传递到过程的外面。试看下面的例程: 

procedure Calculate(CalNo:Integer);

begin

CalNo := CalNo*10;

end; 

          用以下例程调用Calculate函数:



Number := StrToInt(Edit1.Text);

Calculate(Number);

Edit2.Text := IntToStr(Number);

… 

           Number接受由编辑框1输入的数值,经Calculate过程运算。它是一个数值型实参。在进入Calculate函数后,会把Number实参拷贝给形参CalNo,在过程中CalNo增大十倍,但并未传递出来,因此Number值并未改变,在编辑框2中显示仍然是编辑框1中的输入值。形参和实参占用不同的内存地址,在过程或函数被调用时,将实参的值复制到形参占用的内存中。因此出了过程或函数后,形参和实参的数值是不同的,但实参的值并不发生变化。

如果您想改变传入的参数值,就需要使用变量参数,即在被调用程序的参数表中的形参前加上保留字var。例如: 

procedure Calculate(var CalNo : Integer); 

          则CalNo并不在内存中占据一个位置,而是指向实参Number。当一个变参被传递时,任何对形参所作的改变会反映到实参中。这是因为两个参数指向同一个地址。将上一个例程中过程头的形参CalNo前面加上var,再以同样的程序调用它,则在第二个编辑框中会显示计算的结果,把第一个编辑框中的数值放大十倍。这时形参CalNo和实参Number的值都是Nnmber初始值的10倍。

           如果当过程或函数执行是要求不改变形参的值,最保险的办法是使用常量参数。在参数表的参数名称前加上保留字const可以使一个形参成为常量参数。使用常量参数代替数值参数可以保护您的参数,使您在不想改变参数值时不会意外地将新的值赋给这个参数。

2.1.9 定义新的数据类型 

           Object Pascal有一些系统预定义的数据类型,在2.1.2中已经对它们作了介绍。您可以利用这些数据类型以建立新的数据类型来满足程序的特定需要。下面简单地叙述了您能建立的主要数据类型,如枚举型、子界型、数组型、集合型、记录型、对象型等。 

2.1.9.1 枚举类型 

           一个枚举型的说明列出了所有这种类型可以包括的值: 

type

Tdays=( Sunday ,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday);  

         可以定义上述枚举类型的变量:

var

DayOfWeek:TDays;  

        在枚举型中,括号中的每一个值都有一个由说明它的位置决定的整形值。例如Sunday有整形值0,Monday有整形值1等。您可以把DayOfWeek说明为一个整形变量,并将一星期的每一天赋一个整形值以达到相同的效果,但用枚举型会使得程序可读性好,编写容易。当您在枚举型中列出值时,您同时说明了这个值是一个标识符。例如您的程序中如果已经含有TDays类型且说明了DayOfWeeks变量,则程序中便不能使用Monday变量,因为它已经被说明为标识符了。  

2.1.9.2 子界类型 

        子界型是下列这些类型中某范围内的值:整形、布尔量、字符型或枚举型。在您想限制一个变量的取值范围时,子界型是非常有用的。 

type

Thours = 0..23;

TValidLetter = 'A' .. 'F';

TDays = ( Sunday ,Monday,Tuesday,Wednesday,Thursday,

Friday,Saturday); {枚举型}

TWorkDay = Monday..Friday; {一个TDays型的子界} 

        子界型限定了变量的可能取值范围。当范围检查打开时,(在库单元的Implementation后面有{$R*.DFM}字样表示范围检查打开,否则您可以在Options|Project|Complier Options中选择Range Cheking来打开范围检查),如果变量取到子界以外的值,会出现一个范围检查错误。 

2.1.9.3 数组类型 

        数组是某种数据类型的有序组合,其中每一个元素的值由其相对位置来指定,您可以在数组的某个位置上放置数据,并在需要时使用这些数据。下面的类型说明了一个Double型的数组变量:

var

Check : array [1..10] of Double; 

        它表示Check指向一个含有10个Double型元素的数据串列,代表每一个元素的是1到10之间的数字,称为索引。数组的每一项由数组名称加上[]中的索引来表示。Check包含10个变量,Check[1]表示第一个变量。您也可以把数组定义成类型:

type

TCheck = array[1..10] of Double;

则变量说明改为:

var

Check :TCheck; 

        您可以通过给数组赋值等方法来使用数组。下面的语句将0.0赋给Check数组中的所有元素: 

for J := 1 to 10 do

Check[J] := 0.0;

        数组也可以是多维的,下面的类型定义了一个20行、20列的数组。

type

Ttable = array[1..20,1..20] of Double;

var

table1:TTable; 

       想将这一表格的所有数据初始化为0.0,您可以使用for循环: 

var

Col,Row:Integer;



for Col :=1 to 20 do

for Row := 1 to 20 do

Table1[Col,Row] := 0.0; 

2.1.9.4 字符串类型 

        字符串类型事实上是一个一维的字符数组。当您说明一个字符串型的变量时,您应当指明这个字符串的大小,下面是说明字符串类型的例子:

type

MyString: string[15];

var

MyName: MyString; 

        则变量MyName被说明成为最多可以包含15个字符。如果您没有说明字符串的大小,Delphi会认为字符串包含最大值255个字符。给字符串赋值可以直接使用单引号括起的字串赋值: 

MyName := 'Frank.Smith';

或MyName := '张明'; 

        因为MyName是一个可以包含15个字符的MyString型变量,上文的两个的变量都是有效的,一个汉字可以视作两个字符。当您给字符串型变量赋的值多于定义数值时,例如将MyName赋为‘FrankSmith.Franklin’,则Delphi只会接受前15个字符‘FrankSmith.Fran’。在内存中,字符串通常占用比所说明的大小多一个字节的空间,因为第一个位置是一个包含这个数组大小的字节。您可以使用索引值来访问字符串的字符,MyName[1]可以得到MyName的第一个字符'F'。

         您可以使用Delphi丰富的运算符、过程和函数来处理字符串型的变量和属性。下面介绍几个常用的运算符和Delphi过程或函数:

         Concat和(+)功能相同,都可以将多个字符串组合在一起,建立一个较大的字符串;Copy会返回一个字符串中的子字符串;Delete在一个字符串中从一个指定位置起删除一定数目的字符;Insert在一个字符串中插入一个字符串;Length返回字符串的长度;Pos返回一个子字符串在一个字符串中的位置,即索引值。 

2.1.9.5 集合类型 

         集合类型是一群相同类型元素的组合,这些类型必须是有限类型如整形、布尔型、字符型、枚举型和子界型。在检查一个值是否属于一个特定集合时,集合类型非常有用。下面的例程可以说明集合类型的用法:

         在窗体上加入一个编辑框和一个按钮,清除编辑框中的文字,在其上加上Caption为“输入元音”的标签Label,并在编辑框的下方加入一个空的标签,将按钮的Default属性改为True,建立按钮的事件处理过程如下: 

procedure TForm1.Button1Click(Sender:TObject);

type

Tvowels=set of Char;

var

Vowels:TVowels;

begin

Vowels := ['a','e','i','o','u'];

if Edit1.Text[1] in Vowels then

Lable2.Caption := '是元音';

else

Lable2.Caption := '请再试';

end; 

        执行这个程序,在编辑框中输入字母,表达式Edit1.Text[1] in Vowels的结果是布尔型的,in是运算符,用来判断字母是否存在于集合中。输入的判别结果会显示在编辑框的下方。以上就用到了集合类型TVowels。 

2.1.9.6 记录类型 

        记录是您的程序可以成组访问的一群数据的集合。下面的例程说明了一个记录类型的用法: 

type

TEmployee=record

Name : string[20];

YearHired:1990..2000;

Salsry: Double;

Position: string[20];

end; 

      记录包含可以保存数据的域,每一个域有一个数据类型。上文的记录TEmployee类型就含有四个域。您可以用以下的方式说明记录型的变量: 

var

NewEmployee,PromotedEmployee:TEmployee;

       用如下的方法可以访问记录的单域:

NewEmployee.Salary := 1000;

编写如下的语句可以给整个记录赋值: 

with PromotedEmployee do

begin

Name :='';

YearHired := 1993;

Salary := 2000.00

Position := 'editor';

end; 

       您的程序可以将记录当成单一实体来操作: 

PromptEmployee := NewEmployee;

     以上介绍了用户常用的自定义类型。在Delphi的编程中,对象是非常重要的用户自定义数据类型。象记录一样,对象是结构化的数据类型,它包含数据的域(Field),也包含作为方法的过程和函数。在Delphi中,当您向窗体中加入一个部件,也就是向窗体对象中加入了一个域;每一个部件也是对象,每当您建立一个事件处理过程使得部件可以响应一个事件时,您即自动地在窗体中加入了一个方法。在本章第2节中,将详细讲述Delphi面向对象编程的方法和技巧。 

2.1.10 Object Pascal的库单元Unit 

        Units是常量、变量、数据类型、过程和函数的集合,而且能够被多个应用程序所共享。Delphi已经拥有许多预定义的程序库单元可供您建立您的程序库单元使用。Delphi的Visual Component Library由多个程序库单元组成,它们说明了对象、部件以供您的应用程序用来设计用户界面。例如,当您在窗体中加入一个Check Box时,Delphi自动在您的程序库单元中加入了Stdctrls库单元,因为TCheckBox部件是在StdCtrls库单元中说明的。

       当您设计您的窗体时,Delphi自动建立一个和您的窗体有关的库单元。您的库单元不必都和窗体有关,也可以使用预定义的只包含数学运算函数的库单元,或是自行编写数学函数库单元。在一个库单元中所有的说明都相互有关系,例如,CDialogs程序库单元包含了在您的应用程序中使用的普通对话框的所有说明。 

2.1.10.1 Object Pascal程序库单元的结构 

       不管一个库单元是否和一个窗体有关,库单元的结构都是相同的。其结构如下: 

unit <库单元名称> 

interface 

uses <选择性的库单元列表>

{公有说明} 

implementation 

uses <选择性的库单元列表>

{私有说明}

{过程和函数的执行部分}

    initialization {选择性的}

{选择性的初始化程序}

end. 

2.1.10.2 程序库单元的接口部分 

        interface是库单元的接口部分,它决定了本库单元对其他任何库单元或程序的可见(可访问)部分。您可以在接口部分说明变量、常量、数据类型、过程和函数等等。Delphi在您设计窗体的库单元中,将窗体数据类型、窗体变量和事件处理过程都说明在这一部分。

       interface标志库单元接口部分的开始。在interface中的说明对要使用这些说明的其他库单元或应用程序是可见的。一个库单元可以使用其他Unit的说明,只需要在uses子句中指明那些库单元即可。例如,您在库单元A中编写程序代码,且您想调用UnitB于interface部分说明的程序。您可以把库单元B的名称加入到A的interface部分的uses子句中,则任何A中的程序都可以调用B中说明的程序。而且,如果B中interface部分的uses子句中出现C库单元,尽管A中未曾出现C,A同样可以调用B、C库单元在interface中说明的程序。但如果B出现在A的interface部分的uses子句中,那么库单元A便不能出现在B的interface的uses子句中。因为这样会产生对库单元的循环访问。当试图编译时,会产生出现错误信息。 

2.1.10.3 程序库单元的实现部分 

        实现部分implementation中包含interface中说明的过程、函数、事件处理过程的具体实现程序代码。这一部分可以有自己的额外说明,但这些说明是私有的,外部程序不能调用这些说明。在interface中说明的函数实体必须在implementation部分出现,可以使用标题简写:只输入procedure或function保留字,后面跟过程或函数的名称即可,其后则是程序的实现部分了。如果您在implementation部分说明任何常式,其标题并未出现在interface部分,则必须写全其标题部分。

        在implementation部分的uses子句中指定的库单元,只供给本库单元的程序使用其interface中说明的程序。其他使用本库单元的库单元,不能访问这些在implementation的udes子句中库单元的说明,因为在implementation后进行的库单元包含是私有的。所以上例中,如果C出现在B的implementation部分,则A不能使用C的公有部分,除非C出现在A的uses子句中。在implementation中出现的循环访问是Delphi所允许的,如果A的implemetation的uses子句中出现B,则B的implementation部分也可以出现A。 

2.1.10.4 程序库单元的初始化部分 

        初始化当前库单元所使用的数据,或是通过interface部分将数据提供给其他应用程序、库单元使用时,您可以在库单元中加入一个initialization部分,在库单元的end前加上您的初始化语句。当一个应用程序使用一个库单元时,在库单元中的initialization部分会先于其他的代码执行。如果一个应用程序使用了多个库单元,则每一个库单元的初始化部分都会在所有的程序代码前执行。 

2.1.10.5 使用Delphi的可视化部件及其库单元 

       当您在窗体中加入可视化部件时,如果该部件在可视化部件库中,Delphi会在您的库单元的interface部分的uses子句中自动加上需要使用的库单元名称。但有些对象在Delphi的环境中并没有可视化部件存在,例如,您想在库单元中加入一个预定义的信息框,则您必须把MsgDlg库单元加入您的uses子句中。如果您要使用TPrinter对象的话,必须将Printer库单元加入uses子句中。在在线帮助中可以查到对象所属的预定义库单元。

        要使用在其他库单元中说明的函数,应在函数的前面加上这一库单元的名称,并用‘.’号隔开。例如,要在Unit2中使用Unit1中说明的Calculate函数,应使用下面的方法:

Number := Unit1.Calculate(10);

         您可以在任何标识符如属性、常量、变量、数据类型、函数等之前加上库单元的名称。您可以在自由地在任何Delphi库单元中加入程序代码,但不要改变由Delphi生成的程序。 

2.1.10.6 建立与窗体无关的新库单元 

        如果您想在工程中建立一个和任何窗体无关的新库单元,可以现选用File|New Unit。这时一个新的库单元加入了工程,新库单元的代码如下: 

unit Unit2;

interface

implementation

end. 

         Delphi将根据您的工程中的文件数目为您的库单元选择名称,您可以在程序骨架间加入您的程序代码。

          当编译您的工程时,这个新加入的库单元会被编译为一个具有.DCU后缀的文件。这个新生成的文件是链接到工程的可执行文件上的机器代码。


想死你们了!

TOP

DELPHI基础教程

第二章 Delphi面向对象的编程方法(四)
2.1.10.7 将库单元加入工程 

        将库单元加入工程是比较简单的。无论是您自己建立的库单元还是Delphi建立的与窗体有关的库单元,如果已经完成,则先打开您想加入库单元的工程(可以用Open Project打开工程);再选用File|Open File,然后选择您想加入的源程序(.PAS文件),并选择OK即可。则库单元被加入到应用程序中。 

2.2 用Delphi的对象进行编程 

        Delphi是基于面向对象编程的先进开发环境。面向对象的程序设计(OOP)是结构化语言的自然延伸。OOP的先进编程方法,会产生一个清晰而又容易扩展及维护的程序。一旦您为您的程序建立了一个对象,您和其他的程序员可以在其他的程序中使用这个对象,完全不必重新编制繁复的代码。对象的重复使用可以大大地节省开发时间,切实地提高您和其他人的工作效率。 

2.2.1 什么是对象 

        一个对象是一个数据类型。对象就象记录一样,是一种数据结构。按最简单的理解,我们可以将对象理解成一个记录。但实际上,对象是一种定义不确切的术语,它常用来定义抽象的事务,是构成应用程序的项目,其内涵远比记录要丰富。在本书中,对象可被理解为可视化部件如按钮、标签、表等。

        了解对象,最关键的是掌握对象的特性。一个对象,其最突出的特征有三个:封装性、继承性、多态性。 

2.2.1.1 对象的封装性 

        对对象最基本的理解是把数据和代码组合在同一个结构中,这就是对象的封装特性。将对象的数据域封闭在对象的内部,使得外部程序必需而且只能使用正确的方法才能对要读写的数据域进行访问。封装性意味着数据和代码一起出现在同一结构中,如果需要的话,可以在数据周围砌上“围墙”,只有用对象类的方法才能在“围墙”上打开缺口。 

2.2.1.2 对象的继承性 

        继承性的含义直接而且显然。它是指把一个新的对象定义成为已存在对象的后代;新对象继承了旧类的一切东西。在往新对象中添加任何新内容以前,父类的每一个字段和方法都已存在于子类中,父类是创建子类的基石。 

2.2.1.3 对象的多态性 

        多态性是在对象体系中把设想和实现分开的手段。如果说继承性是系统的布局手段,多态性就是其功能实现的方法。多态性意味着某种概括的动作可以由特定的方式来实现,这取决于执行该动作的对象。多态性允许以类似的方式处理类体系中类似的对象。根据特定的任务,一个应用程序被分解成许多对象,多态性把高级设计处理的设想如新对象的创建、对象在屏幕上的重显、程序运行的其它抽象描述等,留给知道该如何完美的处理它们的对象去实现。 

2.2.1.4 通过Delphi实例了解对象 

      让我们结合Delphi的实例讨论对象的概念:

      当您要建立一个新工程时,Delphi 将显示一个窗体作为设计的基础。在程序编辑器中,Delphi将这个窗体说明为一个新的对象类型,并同时在与窗体相关联的库单元中生成了创建这个新窗体对象的程序代码。 

unit Unit1; 

interface 

uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs; 

type

TForm1 = class(TForm) {窗体的类型说明开始}

private

{ Private declarations }

public

{ Public declarations }

end; {窗体的类型说明结束} 

var

Form1: TForm1; {说明一个窗体变量} 

implementation 

{$R *.DFM}  

end. 

        新的窗体类型是TForm1,它是从TForm继承下来的一个对象。它具有对象的特征:含有域或方法。由于您未给窗体加入任何部件,所以它只有从TForm类中继承的域和方法,在窗体对象的类型说明中,您是看不到任何域、方法的说明的。Form1称为TForm1类型的实例(instance)。您可以说明多个对象类型的实例,例如在多文档界面(MDI)中管理多个子窗口时就要进行这样的说明。每一个实例都有自己的说明,但所有的实例却共用相同的代码。

         假设您向窗体中加入了一个按钮部件,并对这个按钮建立了一个OnClick事件处理过程。再查看Unit1的源程序,会发现TForm1的类型说明部分如下:

type

TForm1 = class(TForm)

Button1: TButton;

procedure Button1Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end; 

      现在TForm1对象有了一个名为Button1的域:它是您在窗体中加入的按钮。TButton是一个对象类型,Button1是Tbutton的一个实例。它被TForm1对象所包含,作为它的数据域。每当您在窗体中加入一个部件时,部件的名称就会作为TFom1的域加入到类型说明中来。在Delphi中,您所编写的事件处理过程都是窗体对象的方法。每当您建立一个事件处理过程,就会在窗体的对象类型中说明一个方法。

       当您使用Object Inspector来改变对象(部件)的名称时,这个名称的改变会反映到程序中。例如,在Object Inspector中将Form1的Name属性命名为ColorBox,您会发现在类型说明部分,会将前文的TForm1改为: 

TColorBox=class(TForm); 

         并且在变量说明部分,会说明ColorBox为TColorBox类型的变量,由Delphi自动产生的事件处理过程名称会自动改为TColorBox.Button1Click;但您自行编写的实现部分的代码却不会被自动修改。因此,如果您在改变Name属性前编写了程序,则您必须将事件处理过程中的对象名称进行改变。所以,原先的Form1.Color要改为ColorBox.Color。 

2.2.2 从一个对象中继承数据和方法 

        前面的TForm1类型是很简单的,因为它只含有域Button1和方法Button1Click。但是在这个窗体上,您可以改变窗体的大小、加入或删除窗体的最大最小化按钮,或设置这个窗体为MDI界面。对于一个只包含一个域和方法的对象来讲,您并没有看到显式的支持程序。在窗体上单击鼠标或用Object Inspector的上端的Object Selector选中Form1对象,按动F1查阅它的在线帮助,您会在Properties和Method中找到它的继承到的全部属性和方法。这些是在TForm类型中说明的,TForm1是TForm的子类,直接继承了它所有的域、方法、属性和事件。例如窗体的颜色属性Color就是在TForm中说明的。当您在工程中加入一个新窗体时,就等于加入了一个基本模型。通过不断地在窗体中加入部件,您就自行定义了一个新的窗体。要自定义任何对象,您都将从已经存在的对象中继承域和方法,建立一个该种对象的子类。例如对象TForm1就被说明为对象TForm的子类,拥有一个窗体部件的基本属性或方法。只有当您在窗体中加入了部件或编写了事件处理过程时,Form1才成为您自己的类型。

        一个比较特殊的对象是从一个范围较广或较一般的对象中继承下来的,它是这个特别对象的祖先,这个对象则称为祖先的后代。一个对象只能有一个直接的祖先,但是它可以有许多后代。TForm是TForm1类型的祖先,所有的窗体对象都是TForm的后代。

       用F1查阅窗体的在线帮助时,您会发现TForm被称为component(部件)。这是因为所有的部件都是对象。

        在这个结构中所有的部件都是对象。部件类型TComponent从TObject类型中继承数据和程序代码,并具有额外的可以用作特殊用途的属性、方法、事件,所以部件可以直接和用户打交道,记录它的状态并存贮到文件中等等。控制类型TControl从TComponent中继承而来,又增加了新的功能,如它可以显示一个对象。在上图中,虽然TCheckBox不是直接由TObject继承来的,但是它仍然有任何对象所拥有的属性,因为在VCL结构中,TCheckBox终究还是从TObject 中继承了所有功能的特殊对象,但它还有些自行定义的独到的功能,如可以选择记录状态等。 

2.2.3 对象的范围 

2.2.3.1 关于对象的范围 

        一个对象的范围决定了它的数据域、属性值、方法的活动范围和访问范围。在一个对象的说明部分说明的数据域、属性值、方法都只是在这个对象的范围中,而且只有这个对象和它的后代才能拥有它们。虽然这些方法的实际程序代码可能是在这个对象之外的程序库单元中,但这些方法仍然在这个对象的范围内,因为它们是在这个对象的说明部分中说明的。

        当您在一个对象的事件处理过程中编写程序代码来访问这个对象的属性值、方法或域时,您不需要在这些标识符之前加上这个对象变量的名称。例如,如果您在一个新窗体上加入一个按钮和一个编辑框,并为这个按钮编写OnClick事件处理过程: 

procedure TForm1.Button1Click(Sender:Tobject);

begin

Color :=clFuchsia;

Edit1.Color :=clLime;

end;  

        其中的第一行语句是为整个窗体Form1着色。您也可以编写如下:

 

Form1.Color :=clFuchsia; 

        但您可以不必加上Form1.,因为Button1Click方法是在TForm1对象的范围里。当您在一个对象的范围中时,您可以省略所有这个对象中的属性值、方法、域之前的对象标识符。但是当您编写第二个语句改变编辑框的底色时,因为此时您想访问的是TEdit1对象的Color属性,而不是TForm1类型的,所以您需要通过在属性前面加上编辑框的名称来指明Color属性值的范围。如果不指明,Delphi会象第一个语句一样,将窗体的颜色变成绿色。因为Edit1部件是在窗体中的,它是窗体的一个数据域,所以您同样不必指明其从属关系。

       如果Edit1是在其他窗体中,那么您需要在编辑框之前加上这个船体对象的名称了。例如,如果Edit1是在Form2之中,那它是Form2说明的一个数据域,并位于Form2的范围中,那么您需要将第二句改为: 

Form2.Edit1.Color := clLime; 

而且需要把Unit2加入Unit1的uses子句中。

        一个对象的范围扩展到这个对象的所有后代。TForm的所有属性值、方法和事件都在TForm1的范围中,因为TForm1是TForm的后代。您的应用程序不能说明和祖先的数据域重名的类型、变量等。如果Delphi显示了一个标识符被重复定义的信息,就有可能是一个数据域和其祖先对象(例如TForm)的一个数据域有了相同的名称。可以尝试改变这个标识符的名称。 

2.2.3.2 重载一个方法 

        您可以重载(Override)一个方法。通过在后代对象中说明一个与祖先对象重名的方法,就可以重载一个方法。如果想使这个方法在后代对象中作和祖先对象中一样的工作但是使用不同的方式时,您就可以重载这个方法。Delphi不推荐您经常重载方法,除非您想建立一个新的部件。重载一个方法,Delphi编译器不会给出错误或警告提示信息。

2.2.4 对象公有域和私有域的说明 

        当使用Delphi的环境来建立应用程序时,您可以在一个TForm的后代对象中加入数据域和方法,也可以通过直接修改对象类型说明的方法来为一个对象加上域和方法,而不是把一个部件加入窗体或事件处理过程中。

        您可以在对象的Public或Private部分加入新的数据域和方法。Public和Private是Object Pascal的保留字。当您在工程中加入新的窗体时,Delphi开始建立这个新窗体对象。每一个新的对象都包含public和private指示,以便您在代码中加入数据域和方法。在public部分中说明其它库单元中对象的方法也可以访问的数据域或方法。在private部分的说明有访问的限制。如果您在private中说明域和方法,那么它在说明这个对象的库单元外是不透明的,而且不能被访问。private中可以说明只能被本库单元方法访问的数据域和本库单元对象访问的方法。过程或函数的程序代码可以放在库单元的implementation部分。 

2.2.5 访问对象的域和方法 

       当您想要改变一个窗体对象的一个域的某个属性,或是调用它的一个方法时,您必须在这个属性名称或调用方法之前加上这个对象的名称。例如,如果您的窗体上有一个编辑框部件,而您需要在运行中改变它的Text属性,需要编写下列的代码: 

Edit1.Text := 'Welcome to Delphi';

同样,清除编辑框部件中选中的文本,可以调用TEdit部件的相应方法: 

Edit1.ClearSelection; 

        如果您想改变一个窗体对象中一个对象域的多个属性或调用多个方法时,使用with语句可以简化您的程序。with语句在对象中可以和在记录中一样方便地使用。下面的事件处理过程在响应OnClick事件时,会对一个列表框作多个调整: 

procedure TForm1.Button1Click(Sender:TObject);

begin

ListBox1.Clear;

ListBox1.MultiSelect :=True;

ListBox1.Item.Add('One');

ListBox1.Item.Add('Two');

ListBox1.Item.Add('Three');

ListBox1.Sorted :=Ture;

ListBox1.FontStyle :=[fsBold];

ListBox1.Font.Color :=clPurple;

ListBox1.Font.Name :='Times New Roman';

ListBox1.ScaleBy(125,100);

end; 

如果使用了With语句,则程序如下: 

procedure TForm1.Button1Click(Sender:TObject);

begin

with (ListBox1) do

begin

Clear;

MultiSelect :=True;

Item.Add('One');

Item.Add('Two');

Item.Add('Three');

Sorted :=Ture;

FontStyle :=[fsBold];

Font.Color :=clPurple;

Font.Name :='Times New Roman';

ScaleBy(125,100);

end;

end; 

       使用with语句,您不必在每一个属性或方法前加上ListBox1标识符,在With语句之内,所有的属性或调用方法对于ListBox这个对象而言都是在它的范围内的。 

2.2.6 对象变量的赋值 

        如果两个变量类型相同或兼容,您可以把其中一个对象变量赋给另一个对象变量。例如,对象TForm1和TForm2都是从TForm继承下来的类型,而且Form1和Form2已被说明过,那么您可以把Form1赋给Form2:

Form2 :=Form1;

         只要赋值的对象变量是被赋值的对象变量的祖先类型,您就可以将一个对象变量赋给另一个对象变量。例如,下面是一个TDataForm的类型说明,在变量说明部分一共说明了两个变量:AForm和DataForm。 

type

TDataForm = class(TForm)

Button1:TButton;

Edit1:TEdit;

DataGrid1:TDataGrid;

Database1:TDatabase;

TableSet1:TTableSet;

VisibleSession1:TVisibleSession;

private

{私有域说明}

public

{公有域说明}

end;

var

AForm:TForm;

DataForm:TDataForm;

       因为TDataForm是TForm类型的后代,所以Dataform是AForm的后代,因此下面的赋值语句是合法的:

AForm :=DataForm;

        这一点在Delphi中是极为重要的。让我们来看一下应用程序调用事件处理过程的过程,下面是一个按钮部件的OnClick事件处理过程:

procedure TForm1.Button1Click(Sender:TObject);

begin

end;

        您可以看到TObject类在Delphi的Visual Component Library的顶部,这就意味着所有的Delphi对象都是TObject的后代。因为Sender是TObject类型,所以任何对象都可以赋值给它。虽然您没有看见赋值的程序代码,但事实上发生事件的部件或控制部件已经赋给Sender了,这就是说Sender的值是响应发生事件的部件或控制部件的。

       您可以使用保留字is来测试Sender以便找到调用这个事件处理过程的部件或控制部件的类型。Delphi中的一个显示drag-and-drop的DRAGDROP.DPR工程。加载它,可以查阅到DROPFONT.PAS库单元的代码,在Memo1DragOver方法中检查了一个对象变量的类型。在这种情形下,参数是Source而不是Sender。 

procrdure TForm1.Memo1DragOver(SenderSource:TObject;X,Y:integer;

State:TDragState;var Accept:Boolean);

begin

Accept :=Source is TLabel;

end;  

        Source参数也是TObject类型,Source被赋值为那个被拖曳的对象。用Memo1DragOver方法的目的是确保只有标签可以被拖曳。Accept是布尔型参数,如果Accept为True,那么用户选择的部件可以被拖曳;反之当Accept的值为False时,用户就不可以拖曳选择控制部件。is保留字检查Source是否TLabel的类型,所以Accept只有在用户拖曳一个标签时才为真,并作为变参输出到函数之外。

下面的drag-and-drop展示的Memo1DragDrop事件处理过程中也使用了Source参数。这个方法是为了把Memo部件的字型改变成和放入这个备注控制部件的标签一样的字型: 

procedure TForm1.Memo1DragDrop(SenderSource:TObject;

X,Y:Integer);

begin

Memo1.Font := (Source as TLabel).Font;

end; 

         当您在这个事件处理过程中编写赋值语句时,开发人员并不知道用户会放入哪一个标签,只有通过参考这个标签的名称(Source as TLabel)用户才能知道,并把标签类型赋给Memo1.TFont。Source包含了用户拖放控制部件的名称,只有当Source是一个标签时,这个事件处理过程才允许这个赋值发生。

2.2.7 建立非可视化对象 

        您在Delphi中使用的大部分对象都是您在设计和运行期间可以看见的部件,例如编辑框、按钮等;一些部件,如通用对话框(Common dialog box)等,在设计时看不见,而在运行时可以看见;另外有些部件,例如计时器(Timer)、数据源(Data Source)部件等,在程序的运行期间没有任何可视化的显示,但您却可以在您的应用程序中使用它们。 

2.2.7.1说明一个非可视化对象 

        下面,通过一个简单的例子讲述如何建立自己的非可视化对象:

        您可以用如下的方法,建立一个自己的TEmployee非可视化对象: 

type

Temployee = class(TObject);

Name := String[25];

Title := String[25];

HourlyPayRate : Double;

function CalculatePayAmount:Double;

end; 

        在这种情况下,TEmployee从TObject继承下来,且包含三个域和一个方法。把您建立的类型说明放在库单元中的说明部分,并和窗体说明放在一起。在这个程序库单元的变量说明部分,说明一个新类型的变量: 

var

Employee : TEmployee; 

2.2.7.2用Create方法建立对象实例 

        TEmployee只是一个对象类型。除非通过一个构造函数的调用从而被实例取代或创建,否则一个对象并不存储在内存中。构造函数是一个方法,它为新对象配置内存并且指向这个新的对象。这个新的对象也被称为这个对象类型的一个实例。

        建立一个对象的实例,需要调用Create方法,然后构造函数把这个实例赋给一个变量。如果您想说明一个TEmployee类型的实例,在您访问这个对象的任何域之前,您的程序代码必须调用Create。

Employee := TEmployee.Create; 

        Create方法并没有在TEmployee类型中说明,它继承自TObject类型。因为TEmployee是TObject的子类,所以它可以调用Create方法而创建一个TEmployee实例。然后把它赋给Employee变量。在创建了一个这样的对象后,您就可以象使用其他的Delphi对象一样访问Employee对象了。 

2.2.7.3 撤销对象 

        当您使用完对象后,您应该及时撤销它,以便把这个对象占用的内存释放出来。您可以通过调用一个注销方法来撤销您的对象,它会释放分配给这个对象的内存。

        Delphi的注销方法有两个:Destroy和Free。Delphi建议使用Free,因为它比Destroy更为安全,同时调用Free会生成效率更高的代码。

         您可以用下列的语句释放用完的Employee对象: 

Employee.Free; 

         和Create方法一样,Free方法也是TEmployee从TObject中继承过来的。把您的注销放在try…finally程序模块的finally部分,而把对象的程序代码放在try部分是编程的好习惯。这样,即使您的程序代码在使用对象时发生了异常事件,也会确保您为这个对象分配的内存会被释放。关于异常处理和try…finally程序模块的信息以及建立非可视化对象的例子,在后文中还将仔细讲述。


想死你们了!

TOP

DELPHI基础教程

第三章 字符串列表及应用(一)

       Delphi应用程序经常要处理字符串列表,如组合框和列表框中的字符串,TMemo部件的文本行,屏幕支持的字体列表,TNotebook部件的tabs属性,字符串网格的行、列等等。

  虽然应用程序以不同的方法使用这些列表,但Delphi通过一个叫字符串列表(Tstrings)的对象提供统一的界面,并且在不同场合可相互转化。例如,可以在TMemo部件中编辑某一字符串,并把它当成列表框中列表项使用。

  在Delphi集成开发环境中也经常要使用字符串列表。如在Object Inspector窗体的取值栏中常列有Tstrings字符,双击该字符,将弹出字符列表编辑器,如图3.1,在编辑器中可进行编辑、加入、删除等操作 。

  在运行状态时也可以操作字符串列表,常见的字符串列表操作如下:

  ● 列表中操作字符串

  ● 装载、保存字符串列表

  ● 创建字符串列表

  ● 在字符串列表中加入对象

本章将介绍字符串列表的常用操作及简单应用。

3.1 字符串列表的常用操作 

3.1.1 列表中操作字符串 

  在Delphi应用程序中,经常要对列表中的字符串进行操作。例如,设计时修改字符串列表属性。

  常见的字符串操作如下:

  ● 计算列表中字符串数目

  ● 访问指定字符串

  ● 查找字符串的位置

● 往列表中加入字符串

  ● 删除列表中的字符串

  ● 在列表中移动字符串

  ● 复制一个完整的字符串列表

  ● 复制列表中的字符串 

3.1.1.1 计算列表中的字符串数目 

  使用Count属性可计算列表中的字符串数目。Count是只读属性,用以指示列表中字符串列表数目。因为字符串列表是以零开始索引,因而Count比列表的最大索引数大一。

  例如,应用程序想计算当前屏幕支持的字体数目,可查找屏幕对象的字体列表,该列表包含了屏幕支持的所有字体的名字。

      FontCount:=Screen.Fonts.Count;

3.1.1.2 访问指定字符串 

  字符串列表有一个可索引的Strings属性,可象使用字符串数组一样使用Strings。例如,列表中第一个字符串为Strings[0]。因为Strings属性为字符串列表中最常用的属性,Strings属性可做为字符串列表的缺省属性,即使用时可省略Strings标识符。

  要访问字符串中的指定字符,可查找该字符的起始位置或索引。字符串数目是以零开始记数的。如果列表中有三个字符串,其索引范围为0..2。

  以下代码是等价的: 

    Memol.Lines.Strings[0]:='This is the first line.';

                Memol.Lines[0]:='This is the first line.'; 

3.1.1.3 查找字符串的位置 

   Indexof方法可查找指定字符串的位置。Indexof有一个字符串类型的参数,方法返回列表中匹配字符串的位置。如果列表中无匹配字符串,将返回- 1。

             Indexof方法只能查找完整字符串,即必须完全匹配整个字符串。如果只匹配部分字符串,必须编写相应代码。

   以下代码判定列表中是否有指定字符串:

if FileListBox1.Items.IndexOf('AUTOEXEC.BAT') > -1 then

begin

Color := clYellow;

Label1.Caption := 'You are in the root directory!';

end; 

3.1.1.4 在列表中加入字符串 

  有两种方式往列表中加入字符串:可把字符串加到列表的最后,也可插入列表之中。

  要把字符串加至列表尾部,使用Add方法,把字符串作为参数传递。

  要把字符串插入列表中,使用Insert方法,传递两个参数:插入的位置和字符串。

  例如,要把“Three”插入至列表中的第三个位置,使用代码Insert(2,'Three')。如果列表中的字符不到2个,Delphi将产生超出索引范围的异常(关于异常详见十二章)。 

3.1.1.5 在列表中移动字符串 

  应用程序可以在列表中把指定字符串移至另一个位置,如果字符串与某个对象相连,则该对象与字符串同步移动。

  Move方法可实现字符串的移动,它有两个参数:现行位置和要移动的位置。以下代码把第三个字符串移至第五的位置: 

   Move(2,4); 

3.1.1.6 删除列表中的字符串 

  使用Delete方法可以删除指定的字符串。Delete的参数是指定字符串的位置,如果不知道字符串的位置,可使用Indexof方法。

  要删除字符串列表中所有的字符串,可使用Clear方法。

  以下代码删除列表框中的指定字符串: 

    With ListBox1.Items do

begin

if Indexof('bureaucracy')>-1 then

Delete (Indexof('bureaucracy'));

end; 

3.1.1.7 复制完整的字符串列表 

  把一个列表复制到另一个列表相当于把源列表赋值给目标列表,即使列表从属于不同的部件,Delphi也可以进行这种复制。

  复制列表将覆盖掉目标列表,如果要把源列表加到目标列表的尾部,使用Addstrings方法。

  以下代码分别为复制列表和连接列表: 

Outline1.Lines:=ComboBox1.Items;

Outline1.Addstrings(ComboBox1.Items); 

3.1.1.8 重复操作列表中的字符串 

  很多情况需要对表中的每一个字符串进行操作,如改变字符串的大小写。象这种重复操作可以用 for 循环来实现,同时使用列表的整数类型的索引。

  以下代码对列表框的字符串进行重复操作。当用户按下按钮时,对列表框中的字符串进行大小写转换。 

procedure TForm1.Button1Click(Sender: TObject);

var

I: Integer;

begin

for I := 0 to ListBox1.Items.Count -1 do

ListBox1.Items[I] := UpperCase(ListBox1.Items[I]);

end;

3.1.2 装载、保存字符串列表 

  应用程序可以非常方便的把Delphi字符串列表存入文本文件,或者从文本文件中重新装载(或装入另一个不同的列表),字符串列表有专门的方法处理这类操作。

  使用LoadFromFile方法从文件中装载字符串列表,LoadFromFile从文本文件中把每一行字符串装入列表中。

  把列表保存在文件中使用SaveToFile方法,使用时传递文件名的参数。如果文件不存在,SaveToFile将创建它,否则将用列表覆盖现有文件内容。

  以下代码装入AUTOEXEC.BAT的文件,并以AUTOEXEC.BAK为文件名进行备份。 

  procedure TForm1.FormCreat(sender:TObject);

var

FileName:String;

begin

FileName:='C:\AUTOEXEC.BAT';

With Memo1 do

begin

LoadFromFile(FileName)

SaveToFile(ChangeFileExt(FileName,'BAK'));

end;

end; 

3.1.3 创建新的字符串列表 

  大多数情况下,应用程序使用的字符串列表是做为部件的某一部分,因此不必创建列表,但Delphi允许创建不依赖部件的字符串列表。

  值得注意的是程序创建的字符串列表必须在使用完之后,释放列表所占用的内存空间。有两种不同的情况需要处理:一是程序以简单的方式创建、使用、释放字符串列表;二是由程序创建,在运行期间均可能使用,在程序终止前释放。这两种情况主要取决于是创建短期字符串列表还是长期字符串列表。 

3.1.3.1短期字符串列表 

  短期字符串列表用于处理简单事物。程序在同一处创建、使用、释放列表。这是最安全的使用字符串列表的方法。

  因为字符串列表要为自己和它的字符串分配内存,所以要用try..finally对列表进行保护,以确保发生异常后释放列表所占用的内存空间。

  创建短期字符串列表的基本步骤为:

  1. 构造字符串列表对象;

  2. 在try..finally块中使用列表;

  3. 在finally后释放列表空间。

  以下代码创建列表、使用列表、最后释放列表空间: 

  procedure TForm1.Button1Click(Sender:Tobject);

var

TemList:TStrings;

begin

Templist:=TStringList.Create;

try

{ use the string list }

finally

Templist.Free;

end;

end; 

3.1.3.2 长期字符串列表 

  如果要在程序运行的任何时候使用字符串列表,则需在程序开始运行时就创建列表,并在程序终止前释放。

  运行时创建字符串列表的步骤为:

  1. 在程序主窗体对象的域中加入TStringsList类型的域;

  2. 在主窗体的OnCreate事件中创建句柄,该事件句柄在主窗体显示前运行;

  3. 在创建事件句柄后,创建字符串列表对象;

  4. 在主窗体的OnDestroy事件创建句柄,该事件句柄在主窗体消失之前运行。

  这样,在程序运行过程中,任何过程、事件均能访问该字符串列表。

  以下代码在程序中加入了一个Clicklist的字符串列表,用户每按一次鼠标键,程序往Clicklist中加入一字符串,程序结束前把该列表存入文件。 

unit Unit1; 

interface 

uses WinTYpes, WinProcs, Classes, Graphics, Forms, Controls, Apps; 

type

TForm1 = class(TForm)

procedure FormCreate(Sender: TObject);

procedure FormDestroy(Sender: TObject);

procedure FormMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

private

{ Private declarations }

public

{ Public declarations }

ClickList: TStrings; {declare the field}

end; 

var

Form1: TForm1; 

implementation 

{$R *.DFM} 

procedure TForm1.FormCreate(Sender: TObject);

begin

ClickList := TStringList.Create; {construct the list}

end; 

procedure TForm1.FormDestroy(Sender: TObject);

begin

ClickList.SaveToFile(ChangeFileExt(Application.ExeName, '.LOG'));

{save the list}

ClickList.Free; {destroy the list object}

end; 

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

ClickList.Add(Format('Click at (%d, %d)', [X, Y])); {add a

string to the list}

end; 

end.


想死你们了!

TOP

DELPHI基础教程

第三章 字符串列表及应用(二)

3.1.4 往字符串列表中加入对象 

  字符串列表除了能在Strings属性中贮存字符串外,还可以在Objects属性中贮存对象。与Stings一样,Objects也是可以索引的,它是对象的索引。

  在应用程序使用列表中的字符串与列表中是否有对象没有多大关系。除非程序特地访问对象,否则Objects中的内容不变,Delphi只是保存了这些信息,应用程序在必要时对其进行操作。

  有些字符串列表忽略加入的对象。如TMemo部件中代表行的列表对加入其中的对象不保存。还有一些字符串列表,把对象与字符串联系起来,如TNotebook部件的Pages属性,它同时保存着页的名字和代表页的对象。如果应用程序往Pages中加入或删除字符串,Delphi自动的加入或删除与之相应的对象。

  虽然程序可分配任何类型的对象到列表中,但最常用的是在自画式控制中把位图与字符串联系起来,注意位图与字符串成对使用。

  Delphi在释放对象的内存空间时并不破坏与之相应的字符串。 

3.1.4.1 操作字符串列表中的对象 

  对于字符串的每一种操作方法,列表中的对象均有相应的方法。例如,应用程序可利用对象的索引来访问对象。与字符串不同的是,不能省略Objects,因为Strings才是列表的缺省属性。

  表3.1中总结了字符串对字符串和对象操作的方法。 

表3.1 TStrings的字符串属性和对象操作属性的方法

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

                操 作    字 符 串        对   象

───────────────────────────────

   访  问      Strings属性                    Objects属性

   加入项目      Add 方法       AddObjects方法

   插入项目      Insert方法      InsertObjects方法

           项目定位      Indexof方法                  IndexofObject方法

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  Delete,Clear,More操作整个项目,即删除字符串时把相应的对象也删除了。但LoadFromFile,SaveToFile方法只对字符串进行操作。 

3.1.4.2 加入对象 

  如果把对象与已存在的字符串联系起来,Delphi将分配给该对象同样的索引号。例如,一个叫Fruits的列表中有字符串('apple'),程序可将名为AppleBitmap的位图与apple字符相联系。 

  With Fruits do Objects[Indexof('apple')]:=AppleBitmap; 

另一种方法是调用列表的AddObject方法,AddObject有两个参数:字符串和对象,如下: 

  Fruits AddObject('Apple',AppleBitmap); 

3.2 字符串列表应用 

          Delphi应用程序经常要用到字符串列表,我们编写的strlist. dpr是应用字符串列表的简单程序。程序运行状态如图3.2所示。列表框列出了屏幕支持的各种字体名称,并且以名称所代表的字体显示在列表中;Tabs的标签不只以字符串来表示,而且附有位图。这就是所谓的自画式控制。下面介绍字符串列表在自画式控制中的应用。

         列表框、组合框、Tabset部件中有一个叫“自画(Ownerdraw)”的风格,它能替代Windows的文本输出,部件的自画式控制在运行状态对每个项目进行重新绘制。最常用的是用图像代替文本输出。

  自画式控制有一个共同特点:它们都包含有项目列表,缺省情况下这些列表就是字符串列表,Windows把它们当成文本显示。Delphi可以把字符串列表与某一对象相联系,这就使得应用程序能用对象来绘制项目。

  通常,创建自画式控制有以下三个步骤:

  1. 设置自画风格;

  2. 把图像对象加入字符串列表中;

  3. 绘制自画项目。 

3.2.1 设置自画风格 

  每个能进行自画式控制的部件都有一个叫Style的属性,Style决定部件是以缺省方式还是以自画方式绘制项目。

  对于列表框和组合框,也有自画式风格选项,表3.2列出了Style的取值及含义。 

表3.2 Style的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

 Style              含  义         举   例

─────────────────────────────────

 

 Fixed          每个项目有相同的高度      1bOwnerDrawFixed

 

        高度由ItenHeight属性决定   csOwnerDrawFixed

Varible          每个项目有不同的高度     1bOwnweDrawVarible

 

        由运行数据决定                      csOwnerDrawVarible

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  tab-set与字符串网格的Style属性通常是Varible.

在Strlist程序中,列表框与tab-set取值如表3.3: 

表3.3 列表框与tab-set的取值

━━━━━━━━━━━━━━━━━━━━━━━━━━

    名称         style

──────────────────────────

ListBox1                              lbOwnerDrawVariable

Tabset1                                tsOwnerDrawVariable

━━━━━━━━━━━━━━━━━━━━━━━━━━━

3.2.2 把图像加入字符串列表 

  上节已介绍如何把对象加入字符串列表,例程把位图对象加入Tabset1的Tabs中: 

procedure TForm1.FormCreate(Sender: TObject);

var

Bitmap: TBitMap;

begin

Listbox1.Items := Screen.Fonts;

Bitmap := TBitmap.Create;

Bitmap.LoadFromFile('PHONE.BMP');

Tabset1.Tabs.AddObject('phone',Bitmap);

Bitmap := TBitmap.Create;

Bitmap.LoadFromFile('PRINTER.BMP');

Tabset1.Tabs.AddObject('printer ',Bitmap);

end; 

3.2.3 绘制自画项目 

  当部件的Style属性是自画式时,Windows不再绘制部件,相反Windows为每个可视项目产生事件,而应用程序必须在事件中绘制项目。

  在应用程序绘制自画控制之前,Windows产生测量项目事件,这个事件告诉程序项目显示的位置 。

  通常由Windows决定项目显示的大小,但应用程序可以处理这个事件并自己选择显示区域。例如,程序要用位图代替文本显示,则需要把区域设置成位图的大小。测量项目事件的名称随部件的名称不同而不同,对于列表框和组合框,该事件叫OnMeasureItem。对于Tabset,该事件叫OnMeasureTab。

  测量项目事件有两个重要参数: 项目索引号与项目的大小。这个大小是变化的。后继项目的输出位置由前面项目的大小决定。例如,在自画式列表框中,如果应用程序把第一个项目的高度设置成5个象素点,则第二个项目在第六个象素点开始输出。列表框和组合框中,应用程序只能设置成项目的高度,而项目的宽度就是部件的高度。在Tabset中,tabs的宽度是可变的,而高度则是固定的。自画式网格允许应用程序改变网格单元的高度和宽度。

  OnMeasureItem的声明如下:

ListBox1 MeasureItem(Control: TwinControl;Index: Integer; var Height: Integer);

  例程中响应OnMeasureItem事件的代码如下:  

procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: Integer;

var Height: Integer);

begin

with ListBox1.Canvas do

begin

Font.Name := ListBox1.Items[Index];

Height := TextHeight('A');

end;

end; 

procedure TForm1.TabSetMeasureTab(Sender: TObject; Index: Integer;

var TabWidth: Integer);

var

BitmapWidth: Integer;

begin

BitmapWidth := TBitmap( TabSet1.Tabs.Objects[Index]).Width;

Inc(TabWidth, 2 + BitmapWidth);

end; 

 在OnMeasureItem事件发生后,Windows激发一个叫OnDrawItem的事件,这个事件也随部件名称不同而不同,常见的有OnDrawItem、OnDrawTab、OnDrawCell。

  OnMeasureItem的声明如下:

  DrawItem( Control: TWinControl; Index: integer; Rect: TRect; State: TOwnerDraw); 

其中Control是包含项目的部件引用

    Index 是项目的索引号

    Rect  是绘制的矩形

    State 是项目的状态,如选中,得到焦点等。 

  在例程的列表框中,所列项目是屏幕支持的各种字体名称,当列表框发生OnDrawItem事件时,程序把输出字体设置成该项目所代表的字体,因而列表框的项目呈现出不同的字体,其代码如下:  

procedure TForm1.DrawItem(Control: TWinControl; Index: Integer;

Rect: TRect; State: TOwnerDrawState);

begin

with ListBox1.Canvas do

begin

FillRect(Rect);

Font.Name := ListBox1.Items[Index];

TextOut(Rect.Left, Rect.Top, ListBox1.Items[Index]);

end;

end;

  在Tabset部件中,则把位图与文本同时输出,其代码如下: 

procedure TForm1.TabSet1DrawTab(Sender: TObject; TabCanvas: TCanvas;

R: TRect; Index: Integer; Selected: Boolean);

var

Bitmap: TBitmap;

begin

Bitmap := TBitmap(TabSet1.Tabs.Objects[Index]);

with TabCanvas do

begin

Draw(R.Left, R.Top + 4, Bitmap);

TextOut(R.Left + 2 + Bitmap.Width,

R.Top + 2, TabSet1.Tabs[Index]);

end;

end;


想死你们了!

TOP

DELPHI基础教程

第四章 文本编辑器的设计(一)

  本章介绍多文本界面(MDI)、多页面界面(MPI)技术;VCL库中TMemo,TEdit 控件以及有关文本编辑的常用对话框的使用。我们开发的MPIEdit.dpr是一个文本编辑的实用程序,可实现如下功能:

  ● MDI的编辑环境

  ● MPI的编辑环境

  ● 创建打开、编辑、保存文件

  ● 查找、替换文件中指定的字符串

  ● 复制、粘贴、剪切字符串

  ● 设置文件字体大小

  ● 打印文件 

  本章将通过MPIEdit实用程序逐一介绍在Delphi中如何实现上述功能。

  文本编辑器是一种常用的应用程序。用户在编辑器中编辑多种文件,在多个文件之间进行数据交换,对文件进行各种属性设置,并按自己要求打印文件。 

4.1 多文本界面 

  多文本界面是一种在一个应用程序中同时打开两个或更多文件的界面形式。例如在字处理程序可同时打开多个文件,用户可在多个文件中方便地进行切换.

MDI应用程序提供了一种方便的方式,使得用户在同一工作区域内对多个文档进行观察和交换数据。MDI工作区域可分为父窗体和子窗体,在Dephi的MDI应用程序中,父窗体通常是程序的主窗体。

  在MDI中,父窗体之外的窗体称为子窗体,文档或其它数据在子窗体打开。这些文档可以是相同的文件格式,或在应用程序支持下也可以是不同的文件格式。

  在设计阶段,可创建 MDI 父窗体作为应用程序主窗体, 亦可创建子窗体样板。Delphi允许创建多个子窗体类型,但MDI应用程序只支持其中的一种。

  本节讲述创建MDI应用程序的基本步骤:

  ● 创建主窗口

  ● 创建子窗口

  ● 创建主窗口菜单

  ● 融合菜单

  ● 运行时创建子窗口 

4.1.1 创建父窗口 

  在MDI应用程序中,主窗口为应用文档提供一个工作区域。这个区域可打开一个或多个子窗口,创建父窗口是建立MDI应用程序的第一步。

  创建父窗口与其它窗口类似,不同之处在于设置窗体的FormStyle属性。

  FormStyle属性可决定一个窗体是父窗口还是子窗口,或不是MDI类型。 只能在设计阶段确定FormStyle。在Object Inspector窗口中将FormStyle属性设置成fsMDIForm。值得注意的是应当把父窗口定义为应用程序的主窗体,否则程序编译会出错。 

4.1.2 创建子窗口 

  设计阶段可创建子窗口的样板,用户在运行进使用样板的实例。子窗口是缺省可见的,如果应用程序在运行进创建子窗口,不要让Delphi自动地创建。

  创建子窗口时将窗体的FormStyle属性设置为fsMDIChild。如果程序在运行时创建子窗口,则

  1. 选择OPtions|Project菜单,系统弹出自动创建列表对话框;

  2. 在自动创建列表中选中子窗口;

  3. 单击>按钮将子窗口移至可得到(Available)窗体列表;

  4. 并单击OK按钮退出。

4.1.3 创建应用程序菜单与菜单融合 

  父窗口的菜单应作为应用程序主菜单。如果子窗口有菜单, 则当子窗口在运行获得焦点并最大化时,子窗口的菜单项将融合父窗口菜单。

  创建父窗口与子窗口菜单的方法与创建普通窗体菜单类似, 详细步骤见第一章。菜单融合是指程序运行过程中,子菜单与父窗口菜单的相互作用。 如当子窗口获得焦点时,子窗口的菜单或插入主窗口的菜单中,或将替换部分或全部的父窗口菜单。

  进行菜单融合需设置的两个属性:

  ● 窗体的Menu属性

  ● 菜单项的GroupIndex属性

       Menu属性定义窗体的活动菜单,而菜单融合只对活动菜单进行。 如果窗体有多个菜单部件,运行时可通过以下代码进行改变:

  Form1.Menu := SecondMenu; 

        GroupIndex属性决定出现在菜单条中各菜单项的位置,在菜单融合中,GroupIndex 将

决定融合菜单是插入还是替换主窗体菜单条中的菜单。

  GroupIndex的缺省值是0,可以用下规则确定其值:

  1. 数值越小,菜单的位置越靠左。

  例如:GroupIndex为0的菜单将出现在菜单条中的最左端。随着GroupIndex数值的增大,菜单项依次向右排列。

  2. 若需替换主菜单中的某一菜单项,则将子菜单相应菜单项的GroupIndex设为与之相等的值。这条规则适合一个或多个菜单项。例如,主菜单中的"Edit"菜单项的GroupIndex 的值为1。将子菜单的一个或多个菜单项的GroupIndext的值设为1,则在运行时,这些菜单项替换主窗口的"Edit"菜单。

  将同一窗体的多个菜单项的GroupIndex设为相同值, 原有的排列顺序在菜单融合时将保持

不变。

  3. 若要在菜单融合时插入菜单项,需在主菜单中预留数值“位置”。例如,主菜单的两菜单项数值为0,5,则子菜单GroupIndex数值为1,2,3,4的菜单在融合时将插入其中。

  在使用MDI界面时,用户通常会打开多个窗体。为了使用户方便地进行窗体切换,常设有一个进行切换的菜单项.此菜单列出了打开窗体的名称,当用户选择其中的一个时,程序进行相应的窗体切换。在Delphi的MDI设计时,可非常方便地实现这一功能。方法是将父窗口的WindowMenu设置成该菜单项的名字即可。

4.2 多页面界面 

  多页面界面是一种非常友好的界面形式。它由一个窗体和多个页面组成, 关于每个页面的信息列在窗体底部的标签(Tabs)上,用户可通过选择标签来进行页面切换。 每次只有一个页面显示在窗体中。MPI较MDI使用更为方便,且切换速度更快。本章例程就是多页面界面的例子。另外Delphi集成开发环境中的代码编辑(Code Editor)窗体是MPI应用在文本编辑中的实例。在MPI中,一个窗体内的多个文件可以方便地进行切换和交换数据。

       多页面界面分为静态MPI和动态MPI两种形式。静态MPI的标签数量固定,用户在事先设计好的多个页面上进行切换。象选择对话框(Option Dialog)就属于静MPI。动态MPI的标签数量不固定,由程序根据需要动态的产生或消除,象代码编辑窗体就是动态MPI,程序可根据用户的需要产生多个文本页面,也可以动态地关闭页面。利用Delphi的TNotebook和Ttabset 可十分方便地设计静态MPI。设计动态MPI则需要编写专门的代码。

4.2.1 静态多页面界面 

  TNotebook,TTabSet可用来开发静态多页面界面。TNotebook部件能显示多页, 每页都有相应的控制。通常TNotebook与TTabset配合进行控制。TTabset 有一组水平的标签,每个标签可通过创建字符串列表进行某种控制。

  MPIEDit例程中的主窗体中有一个TNotebook 部件和 TTabSet 部件。 把两个部件的Aglin属性设置成bsTop和bsBotton,使它们分别处在窗体的上下两部分。为了使TTabSet与TNotebook配合工作,使用下代码: 

  TabSet1.Tabs := Notebook1.Page; 

       另外,在TabSet的OnClick事件中定义下如下代码,可使用户在选择标签时开打相应的页。 

  procedure TEditForm.TabSetClick(Sender : TObject);…

  begin

Notebook1.PageIndex := TabSet1.TabIndex;



end; 

        设计静态MPI时,可在部件窗体(Component Palette)的WIN3。1页面中选中TNotebook 部件,然后在Object inspector窗体中双击TNotebook的Pages属性,Dephi 将弹出对话框,用户可以在此确定Notebook的页数和字符串列表,如图4.6。关闭对话框后, 可对每一页进行设计,使用鼠标右按钮弹出快速菜单进行页面切换。

4.2.2 动态多页面界面 

  使用Delphi进行静态MPI设计非常简单,进行动态MPI设计则需编写专门的代码。 对

于一个多页面文本编辑器,应能实现以下功能:

  ● 动态生成页面,每个页面均能进行文本编辑

  ● 动态关闭页面,直到窗体中只有一个页面为止

  ● 页面切换不影响各种文本编辑操作 

  为了实现以上功能,程序中使用了动态页面类(TDynaPage),其定义如下: 

  type TDynaPage = Class(TObject); 

该类可根据需要动态的产生页面, 每个页面上创建了可进行文本编辑的TMeno部件。 

  procedure...

  puclic

CurPage : integer;

FileList : TSringList;

end; 

        CurPage表示当前用户选择的页面数,用户切换、增加、删除页面均影响CurPage 的值,CurPage初如化为零页。FileList存放打开或创建文件的名字以及与这些文件相关的编辑部件TMemo,页面动态创建、删除将影响FilstList的值。

  TNotebook部件创建后至少有一个页面,因此Pages属性不是空值,只要往Pages中加入字符串,Delphi自动地把该字符串与TPage类对象相联系。TPage类是TCustomEdit派生出来的,在对象浏览器(Object Browse)中可观察到TPage的数据成员和方法。静态生成的页面也是 TPage类。

  要创建多页面编辑器,必须从TPage的父件(Parent属件)创建相应编辑部件。但在动态创建页面时,TPage只是一个与字符串相联系的TObject类,不能写成: 

  MemoParent := Notebook1.Pages.Object[ ]; 

在Delphi中,宣称对象和创建对象都是用指针来标识, 因此可用无类型指针进行指针传递。 

  var

Pi : Pointer;

begin

Pi := Notebook1.Pages.Object[];

Memo.Parent := Pi;

end; 

这样就可在TPage上动态创建编辑部件了。

  往Notebook1中动态生成页面时,页面应所相应的切换,TDynaPage. Notebook1.Tabset1有关的属性要作相应的调整。

            TDynaPage的DynaAdd方法定义如下: 

procedure TDynaPage.DynaAdd(Sender:TNotebook;FileName:String);

var

Pi:Pointer;

Memo:TMemo;

begin

Sender.Pages.add(FileName);

Pi:= Sender.Pages.Objects[Sender.Pages.Count-1];

DynaMemo(pi);

DynaPage.FileList.addObject(FileName,Memo1);

EditForm.TabSet1.Tabs := Sender.Pages;

EditForm.Tabset1.TabIndex:=Sender.Pages.Count-1;

EditForm.Notebook1.PageIndex := EditForm.Tabset1.TabIndex;

DynaPage.CurPage:= Sender.Pages.Count-1;

end; 

procedure DynaMemo(Pi:Pointer);

var

Memo:TMemo;

begin

Memo:=TMemo.Create(Pi);

Memo.Parent:=Pi;

Memo.Align:=alClient;

Memo.borderStyle:=bsNone;

Memo.HideSelection:=False;

Memo1:=Memo;

end;

procedure TDynaPage.Del(Sender:TNotebook;No:integer);

var

Pi:pointer;

begin

Sender.Pages.delete(No);

EditForm.TabSet1.Tabs.delete(No);

Filelist.Delete(No);

DynaPage.CurPage:=EditForm.TabSet1.TabIndex;

Sender.PageIndex := EditForm.Tabset1.TabIndex;

Pi:=FileList.Objects[DynaPage.CurPage];

Memo1:=Pi;

EditForm.Caption:=Sender.Pages.Strings[DynaPage.CurPage];

end;

  当用户在多个页面中进行切换时,程序应当保证对当前页面进行编辑。 例如在多页编辑器中,用户选中某一页面,即可对该页面中的文件进行编辑、寻找、设置、打印等。为了实现这一功能,定义了一个TMemo类型的变量:Memo1,该变量没有实例化,每次调用DynaAdd,DynaDel方法均定把TabIndex指定页面的Memo指针传给Memo1。这样在程序运行中,始终有一个实例化的Memo指针赋给Memo1,而菜单中的文本编辑功能均对Memo1进行操作。这种指针传递就能保证对当前页进行操作。

  定义了TDynaPage后,只需在Open,Close菜单项中加入如下代码,即可方便的在用户打开关闭文件时创建成删除页面。 

 procedure TEditForm.Close1Click(Sender: TObject);

begin

if DynaPage.CurPage<>0 then

DynaPage.Del(Notebook1,DynaPage.CurPage);

if Notebook1.Pages.count = 1 then

Close1.Enabled:=False;

end; 

procedure TEditForm.Open1Click(Sender: TObject);

begin

if OpenDialog1.Execute then

begin

if not(OpenFile or NewFile) then

begin

OpenFile:=true;

Open(OpenDialog1.FileName);

Notebook1.Pages.Strings[0]:=ExtractFileName( OpenDialog1.FileName);

TabSet1.Tabs:=Notebook1.Pages;

end

else

begin

DynaPage.DynaAdd( Notebook1, ExtractFileName(OpenDialog1.FileName));

Open(OpenDialog1.Filename);

if Notebook1.Pages.count > 1 then

Close1.Enabled:=True;

end;

end;

end; 

4.3 文本编辑部件及应用 

4.3.1 TEdit 部件 

        TEdit部件是一个标准的编辑框,用户可在编辑框中输入数据。编辑框也可向用户显示数据。编辑时只能读写一行信息。

   TEdit的Text属性存放着用户输入的数据或向用户显示的数据,Modified属性用以标识 Text的数据是否改变,可通过设置Maxlength属性值来限制用户输入字符的个数量,CharCase

          属性可定义编辑框中字符的大小写。如果设计者想禁止用户输入,可将ReadOnly属性设置成真值。编辑框也能用做密码输入框。通过设置PassWordChar 属性的值,可将用户输入的字符在编辑框中显示成指定的字符,如"*"号等。编辑框还可以进行字符选择操作、粘贴、复制和剪切操作。 

4.3.2 TMemo 部件 

       TMemo部件与TEdit部件类似,能向用户显示数据,用户也可输入数据。与TEdit 部件

不同的是,TMemo部件可以处理多行文本,因此主要用于编辑文件。

  TMemo的Text属性只能在运行时才能访问。Modified属性用以标识Text的数据是否改

变,通过设置MaxLength属性值来限制用户输入字符的数量。

  如果把文本当成一个整体进行访问,可使用Text属性;若想逐行访问,则要使用Lines属性。Lines属性能对文件更方便地进行访问。Lines是TStrings类型的,因此可使用Add 、Delete方法,例如在Memo1中加入一行字符串的代码如下:  

Memo1.Lines.Add('Another line is added'); 

        通过Lines属性可以方便地把文件读入部件中,例程中使用下面的代码将文件读入Memo1: 

Memo1.Lines.loadFromFile(Filename). 

从TMemo 部件中剪切、复制、粘贴文本非常方便,只需使用 CutToclipboard ,CopyToClipBroad,PasteFromClipBoard方法,其代码如下: 

  Memo1.CopyToClipboard

  Memo1.CutToClipboard

  Memo1.PasteFromClipboard 

        TMemo有一些属性,用以控制文本的显示效果。ScrollBars属性可以定义部件的水平滚动条和垂直滚动条。当文件字体改变时,使用AutoSize属性可使部件大小做相应的调整。设置WordWrap属性可以实现自动换行。

  例程中Edit|WordWrap菜单项提供了设置WordWrap的功能,并可根据WordWrap的值决定滚动条的形式。当WordWrap为真时,不需要水平滚动条, 并在菜单中作出检查记号。

其代码如下: 

 procedure TEditForm.SetWordWrap(Sender: TObject);

begin

with Memo1 do

begin

WordWrap := not WordWrap;

if WordWrap then

ScrollBars := ssVertical else

ScrollBars := ssBoth;

WordWrap1.Checked := WordWrap;

end;

SetEditRect;

end; 

        TMemo部件提供了一组关于选择文本的属性和方法。如果想在部件成为当前控件时自动选择文本,可设置 AutoSelect 属性。运行时可用SelectAll 方法选中部件的全部文本。 Selstart属性返回选中文本的开始位置,SelText 包含着被选中的文本。SelLength属性返回选中文本的长度,这两个属性可用于字符串的查找和替换。下一节将详细讨论。

  TMemo的Modified属性是一个运行时才能得到的属性,可判断部件被创建时或Modified属性最后一次设置成假值之后,部件上的文本是否修改。如果修改了,Modified 将设成真值,反之假值。

  例程中在关闭文件时将测试文件的modified属性,如果文件修改后尚未保存, 将出现对话框,询问用户是否保存文件,其代码如下: 

  procedure TEditForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);

var

DialogValue: Integer;

FName: string;

begin

if Memo1.Modified then

begin

FName := Caption;

DialogValue := MessageDlg(Format(SWarningText, [FName]), mtConfirmation,

[mbYes, mbNo, mbCancel], 0);

case DialogValue of

id_Yes: Save1Click(Self);

id_Cancel: CanClose := False;

end;

end;

end; 

4.4 常用对话框的使用 

  Delphi的可视部件类库(Vistual Component Liberty)中,有一组对话框部件,在对象选择板的Dialog 页面中可以找到。 本节着重介绍与文件编辑有关的字体对话框(TFontDialog Componement),查找对话框(TFindDialog Componement) ,替换对话框(TReplace Dialog Componement),文件对开对话框(TOpenDIalog Componement).

   应用这几个对话框可对文件进行字体设置、查找、替换等操作,但需要编写相应的代码。 

4.4.1字体对话框部件 

  字体对话框部件在应用程序中产生字体对话框, 用户可在对话框中进行字体选择和属性设置。用户选择字体并按下OK按钮之后,有关信息便贮存在部件的Font属性中。

  应用程序可通过调用字体对话框的Execult方法来显示对话框,当用户选择OK按钮时,Execult返回True值,否则返回Flase值。

应用程序可以使用Options属性来定义字体对话框的显示和行为方式:例如可在对话框中定义一个帮助按钮或指定出现在字体列表框中的字体。有关Options的主要取值如下表4.1: 

表4.1 字体对话框的Options取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

取值           含义

───────────────────────────────────────

AdAnsiOnly 如果是真值,只能使用Window字符集,

fdEffects 如果是真值,对话框中显示颜色列表和效果检查框;用户可使

用效果检查框定义Strikout下划线文本;使用颜色列表定义字体

颜色。

fdForceFontExise   如果是真值,用户在字体组合框中输入字体名后选择OK按钮,

将出现一个用户字体无效的消息框。

fdNoOEMFont    如果是真值,字体组合框中将不显示向量字体。

fdShowHelp 如果是真值,对话框显示Help按按钮。

fdWysiwyg 如果是真值, 只有打印和屏幕均可得到的字体才会出现在字体

            组合框中。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

  例程中(Edit/Font)菜单具有设置文本字体的功能,其代码如下:

 

  procedure TEditForm.SetFont(Sender : TObject);

begin

FontDialog.Font := Memo1.Font;

if FontDialog1.Execult then

Memo1.Fout := FontDialog1.Font;

SetEdit Rect;

end; 


想死你们了!

TOP

DELPHI基础教程

第四章 文本编辑器的设计(二)
4.4.2查找对话框部件 

  查找对话框部件为应用程序提供查找对话框, 用户可使用查找对话框在文本文件中查找字符串。

  可用Execult方法显示查找对话框,如图4.8。应用程序要查找的字符放到FindText属性中。Options 属性可决定查找对话框中有哪些选项。例如, 用户可选择是否显示匹配检查框。Options的常用选项如表4.2所示。

如果用户在对话框中输入字符并选择FindNext按钮,对话框将发生OnFind事件。 

表4.2 查找对话框的Options属性的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

取值           含义

───────────────────────────────────────

frDown 如果是真值,对话框中出现Down按钮,查找方向向下。如果是假

值,Up按钮将被选中,查找方向向上,frDown 值可在设计或运行

时设置。

frDisableUpDown 如果是真值,Up和Down按钮将变灰,用户不能进行选取;如果是

假值,用户可以选择其中之一。

frFindNext 如果是真值,应用程序查找在FindNext属性中的字符串。

frMatchCase 如果是真值,匹配检查框被选中。设计、运行时均可设置。

frWholeWord 如果是真值,整字匹配检查框被选中,设计、运行时均可设置。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  在OnFind事件中可使用Options属性来决定以何种方式查找。Find方法响应查找对话框的OnFind事件。 

  procedure TEditform.Find(Sender: TObject);

begin

with Sender as TFindDialog do

if not SearchMemo(Memo1, FindText, Options) then

ShowMessage('Cannot find "' + FindText + '".');

end;

          其中SearchMemo函数是Search单元中定义的,SearchMemo可在TEdit,TMemo,以及其它TCustomEdit派生类中查找指定的字符串。查找从控件的脱字号(^)开始, 查找方式由Options决定。如果向后查找从控件的StlStart处开始,如果向前查找则从控件的SelEnd处查找。

  如果在控件中找到相匹配的字符串,则字符串被选中,函数返回真值。如无匹配的字符串,函数返回假值。

  特别注意的是TEdit,TMemo中有一个HideSeletion属性,它决定当焦点从该控制转移至其它控制时,被选中的字符是否保持被选中的状态。如果是真值,则只有获得焦点才能保持被选中状态。查找时,焦点在查找对话框上,因此要想了解查找情况,必须将HideSeletion设成假值。控制的缺省值为真值。

  SearchMemo代码如下: 

unit Search;

interface

uses WinProcs, SysUtils, StdCtrls, Dialogs;

const

WordDelimiters: set of Char = [#0..#255] - ['a'..'z','A'..'Z','1'..'9','0']; 

function SearchMemo(Memo: TCustomEdit;

const SearchString: String;

Options: TFindOptions): Boolean; 

function SearchBuf(Buf: PChar; BufLen: Integer;

SelStart, SelLength: Integer;

SearchString: String;

Options: TFindOptions): PChar; 

implementation 

function SearchMemo(Memo: TCustomEdit;

const SearchString: String;

Options: TFindOptions): Boolean;

var

Buffer, P: PChar;

Size: Word;

begin

Result := False;

if (Length(SearchString) = 0) then Exit;

Size := Memo.GetTextLen;

if (Size = 0) then Exit;

Buffer := StrAlloc(Size + 1);

try

Memo.GetTextBuf(Buffer, Size + 1);

P := SearchBuf(Buffer, Size, Memo.SelStart,

Memo.SelLength,SearchString, Options);

if P <> nil then

begin

Memo.SelStart := P - Buffer;

Memo.SelLength := Length(SearchString);

Result := True;

end;

finally

StrDispose(Buffer);

end;

end; 

function SearchBuf(Buf: PChar; BufLen: Integer;

SelStart, SelLength: Integer;

SearchString: String;

Options: TFindOptions): PChar;

var

SearchCount, I: Integer;

C: Char;

Direction: Shortint;

CharMap: array [Char] of Char; 

function FindNextWordStart(var BufPtr: PChar): Boolean;

begin { (True XOR N) is equivalent to

(not N) }

Result := False; { (False XOR N) is equivalent

to (N) }

{ When Direction is forward (1), skip non

delimiters, then skip delimiters. }

{ When Direction is backward (-1), skip delims, then

skip non delims }

while (SearchCount > 0) and

((Direction = 1) xor (BufPtr^ in

WordDelimiters)) do

begin

Inc(BufPtr, Direction);

Dec(SearchCount);

end;

while (SearchCount > 0) and

((Direction = -1) xor (BufPtr^ in

WordDelimiters)) do

begin

Inc(BufPtr, Direction);

Dec(SearchCount);

end;

Result := SearchCount > 0;

if Direction = -1 then

begin { back up one char, to leave ptr on first non

delim }

Dec(BufPtr, Direction);

Inc(SearchCount);

end;

end; 

begin

Result := nil;

if BufLen <= 0 then Exit;

if frDown in Options then

begin

Direction := 1;

Inc(SelStart, SelLength); { start search past end of

selection }

SearchCount := BufLen - SelStart - Length(SearchString);

if SearchCount < 0 then Exit;

if Longint(SelStart) + SearchCount > BufLen then

Exit;

end

else

begin

Direction := -1;

Dec(SelStart, Length(SearchString));

SearchCount := SelStart;

end;

if (SelStart < 0) or (SelStart > BufLen) then Exit;

Result := @Buf[SelStart]; 

{ Using a Char map array is faster than calling

AnsiUpper on every character }

for C := Low(CharMap) to High(CharMap) do

CharMap[C] := C; 

if not (frMatchCase in Options) then

begin

AnsiUpperBuff(PChar(@CharMap), sizeof(CharMap));

AnsiUpperBuff(@SearchString[1],

Length(SearchString));

end; 

while SearchCount > 0 do

begin

if frWholeWord in Options then

if not FindNextWordStart(Result) then Break;

I := 0;

while (CharMap[Result[I]] = SearchString[I+1]) do

begin

Inc(I);

if I >= Length(SearchString) then

begin

if (not (frWholeWord in Options)) or

(SearchCount = 0) or

(Result[I] in WordDelimiters) then

Exit;

Break;

end;

end;

Inc(Result, Direction);

Dec(SearchCount);

end;

Result := nil;

end; 

end.

 4.4.3 替换对话框部件 

  替换对话框部件为应用程序提供替换对话框。如图4.9。它包括查找对话框的所有功能,此外还允许使用者更换被选中的字符串。FindText 属性是应用程序需查找的字符串。ReplaceText属性是被选中字符的替换字符串。Options 属性决定对话框的显示方式。其值如表4.3所示。

与查找对话框一样,替换对话框亦有OnFind 事件。用户输入查找字符串并按FindNext按钮时,发生OnFind 事件。用户选择Replace 或ReplacAll 时, 对话框发生OnRelpace事件,要替换的字符串存入ReplaceText属性中,要编写相应的代码以支持替换功能。 

 表4.3 替换对话框的Options属性的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

取值              含义

────────────────────────────────────────

frRelpace 如果是真值, 应用程序将ReplaceText 属性中的字符串替换

             FindText属性中的字符串。

frReplacAll 如果是真值,应用程序将ReplaceText属性中的字符串替换,

             查找到的所有FindText属性中的字符串。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  例程中TEditForm.Replace方法响应OnReplace事件,Replace方法首先判断控制中被

选中字符串是否与替换字符串相等,如果不等则进行替换。而后根据Options中的方式循

环进行查找替换。直至无匹配字符串为止。其代码如下: 

  procedure TEditForm.Replace(Sender: TObject);

var

Found: Boolean;

begin

with ReplaceDialog1 do

begin

if AnsiCompareText(Memo1.SelText, FindText) = 0 then

Memo1.SelText := ReplaceText;

Found := SearchMemo(Memo1, FindText, Options);

while Found and (frReplaceAll in Options) do

begin

Memo1.SelText := ReplaceText;

Found := SearchMemo(Memo1, FindText, Options);

end;

if (not Found) and (frReplace in Options) then

ShowMessage('Cannot find "' + FindText + '".');

end;

end; 

4.4.4 打开对话框部件 

  打开对话框部件为应用程序显示打开对话框。使用Execute方法可显示打开对话框用户通过选择文件类型下拉框中的文件类型,可以确定显示在文件列表中的文件。 例如,如果用户选择*.txt文件类型,那么只有在当前目录下的文本文件才会显示在文件列表中。文件扩展名通常也称为过滤器。

  打开对话框包含一个Filters(过滤器)的属性,它可确定文件类型和在文件类型下拉框中的顺序。应用程序可以为打开对话框定义多个过滤器,对话框的FilterIndex 属性可以决定哪个过滤器是文件类型下拉框中的缺省过滤器。如FilterIndex等于2,表示程序运行时出现在文件类型下拉框的过滤器是第2个过滤器。

  例程中关于文件打开的代码如下: 

  procedure TEditForm.Open/Click(Sender : TObject);

begin

if OpenDialog/.Execult then

begin

 …

    Open(Open Dialog/.FileName)

end

end;

  打开,保存对话框中的Options属性值见表4.4 

表4.4 打开、保存对话框的Options属性取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

值               含义

──────────────────────────────────────

 

ofAllowMultiSelect 如果是真值,则允许在文件名列表中选择多个文件。

ofCreatePrompt 如果是真值,当用户在文件编辑框中输入一不存在的文件名,

            并选择OK按钮,则会出现消息框, 提示用户此文件不存在并

            询问是否以此文件名创建一新文件。

ofExiengronDifferent 如果是真值,从对话框中返回的文件扩展名将不同于缺省扩展名。

其值存入DefaultExt属性中。

ofFileMustExist   如果是真值, 当用户在文件编辑框中输入一个不存在的文件名时,

并选择OK按钮, 则会出现一消息框提示用户此文件不存,并询

问是否输入了正确的路径和文件名。

ofNoChangeDir 如果是真值,当前目录将设置成对话框第一次出现的目录,并忽

略任何目录改变。

ofOverWritePrompt 如果是真值,当用户试图保存一个已存在的文件时, 将出现一消息

框,提示用户此文件已存在,并询问是否覆盖。

ofPathMastExit 如果是真值,用户在文件名编辑框只能输入有效路径名, 否则出

现消息框,提示用户路径无效。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

表4.4 打开、保存对话框中的Options属性取值及含义

文件保存对话框与打开对话框类似,如图4.11。它的Option属性见上表。例程在保存文件前先对文件进行读写判断,如果文件是只读文件或未指定文件名的新文件, 则程序对文件不保存,否则备份文件。代码如下:

  procedure TEditForm.Save1Click(Sender: TObject);

procedure CreateBackup(const Filename: string);

var

BackupFilename: string;

begin

BackupFilename := ChangeFileExt(Filename, BackupExt);

DeleteFile(BackupFilename);

RenameFile(Filename, BackupFilename);

end; 

function IsReadOnly(const Filename: string): Boolean;

begin

Result := Boolean(FileGetAttr(Filename) and faReadOnly);

if Result then MessageDlg(Format('%s is read only.',

[ExtractFilename(Filename)]), mtWarning, [mbOK], 0);

end; 

begin

if (Filename = '') or IsReadOnly(Filename) then

SaveAs1Click(Sender)

else

begin

CreateBackup(Filename);

Memo1.Lines.SaveToFile(Filename);

Memo1.Modified := False;

end;

end;

其中CreateBackup过程用以改变需备份文件的扩展名。IsReadOnly 用以判断文件属性。 

4.5 文件打印 

  在Delphi中,文件打印有两种方式:

  1. 将文件变量分配给打印机,用此变量名创建或打开文件后, 往此文件变量写入的任何文本都视为向打印机输出,以下过程可实现文件的打印。 

  procedure TEditForm,Print1Click(Sender: TObject);

var

Line: Integer;

PrintText: System.Text;

begin

if PrintDialog1.Execute then

begin

AssignPrn(PrintText)

Rewrite(PrintText);

Print.CanvasFont := Memo1.Font;

For Line := 0 to Memo1.Lines.Count - 1 do

Writeln(PrintText,Memo1.Line[line];

System.Close(PrintText);

end;

end; 

2. 利用Printers单元中定义的TPrinter对象进行文件打印,本章例程采用这种方法打印文件。 

4.5.1 TPrinter对象 

  TPrinter对象可调用Windows的打印机,在Printer 单元中定义了TPrinter 的实例Printer,用户可直接使用。

  调用TPrinter的BeginDoc方法可开始一项打印工作,调用EndDoc 方法可结束一项已成功发送给打印机的工作。如果在发送过程中出现问题或用户想中途终止打印工作,可调用Abort方法。

  通过检查Printing属性可测试当前是否有打印工作,如果打印工作被终止,Abort属性为真。

  Canvas属性代表打印表面,Brush,Font,Pen属性可决定打印字体或图像的特征。

  Printers属性中包含着已安装的打印机列表,PrinterIndex 属性是当前选择的打印

机,Fonts属性中有当前打印机支持的字体。Orientertion属性可决定打印方向。

  PageHeight,PageWith中包含着当前的高度和宽度。PageNanber为当前页的值。

  设置Title属性可决定在Windows打印管理器或网络中出现的文本。 

4.5.2 TPrintDialog打印对话框 

  TPrintDialog部件显示一打印对话框。用户在对话框中,可以选择打印机、打印页数、打印份数。当用户选择对话框中的Setup按钮,则出现打印设置对话框。

  调用Execute方法显示打印对话框。如图4.12。使用Option属性可设置打印对话框显示的形式。Options的设置如表4.5所示。

  PrintRange属性可定义打印的范围。如果PrintPage的值是prPageNums,则可以设置FromPage和ToPage属性来确定打印范围。设置MinPage,MaxPage属性可限制用户的打印范围。 

表4.5 打印对话框的Option属性的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

取值              含义

──────────────────────────────────────

PoHelp 如果是真值,对话框出现帮助按钮。

PoPageNums 如果是真值,页数按钮有效,用户可以设置打印范围。

PoPrintToFile 如果是真值,文件打印检查框将出现在对话框中,用户可以选

择文件打印。

PoSelection 如果是真值,选择按钮有效, 用户可打印文件中所选择的文本。

PoWarning 如果是真值,在打印机尚未安装时,用户选择OK 按按钮将出

现警告信息。

PoDisablePrinttoToFile 如果是真值,而PoPrintToFile亦是真值时,当对话框出现时,文

件打印对话框将无效。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

       本章例程是利用Printer的画布进行文本打印的。用户选择打印菜单后,将弹出打印对话框,用户可设置各种参数。当用户选择打印按钮后,打印工作进行发送,此时将弹出打印取消对话框,见图4.13, 用户可中止打印工作。有关打印和打印取消的代码如下:  

procedure TEditForm.Print1Click(Sender: TObject);

var

DistanceLine,Line: Integer;

PrintText: System.Text;

begin

if PrintDialog1.Execute then

begin

Printer.Canvas.font := Memo1.Font;

DistanceLine := Trunc(1.5*FontDialog1.font.size);

OpenPrintCancelDialog;

Printer.BeginDoc;

for line := 0 to Memo1.Lines.Count - 1 do

begin

Printer.canvas.textout(0,DistanceLine*Line,Memo1.lines[Line]);

end;

Printer.EndDoc;

BtnBottomDlg.free;

end;

end;

 

procedure TEditForm.OpenPrintCancelDialog;

begin

BtnBottomDlg := TBtnBottomDlg.Create(Application);

BtnBottomDlg.show;

BtnBottomDlg.canvas.Brush.Color := clActiveBorder;

BtnBottomDlg.canvas.TextOut(50,20,'Print'+FileName);

BtnBottomDlg.canvas.TextOut(30,40,'if you want to

stop, please choice Cancel Button.');

end;


想死你们了!

TOP

Delphi实际上是Pascal语言的一种版本
从这里完全可以断定Pascal是Delphi他祖宗了
谁不怀念苏联,谁就没有良心;谁想回到苏联,谁就没有头脑.

Woodu.ME--从零开始的博客生活

TOP

DELPHI基础教程

第五章 Delphi图形图像编程(一)
        在Delphi中,专门定义了一组对象和部件用以绘制图形,完成一些简单的图像功能。利用这些对象、部件的方法,可以方便地绘制各种常用图形;通过设置它们的属性,能得到不同风格的图形。另外,通过对鼠标事件的定义,可以方便的设计图形绘制程序。

  本章将介绍以下内容:

  1. TCanvas,TPen,TBrush,TColor对象的方法及属性;

  2. 绘图功能的实现;

  3. TImage,TPicture,TBitBtn,TBitmap部件的方法及属性;

  4. 图像观测及处理。

  Graphex.dpr是一个简单的图形图像应用程序,是对以上这些对象和组件的具体应用。本章将结合此程序进行讲述。  

5.1 图形对象概述 

5.1.1 TCanvas Object(画布对象)

  TCanvas对象是一个用于绘图的表面,在这个区域上,程序可实现各种绘图功能,很多部件(如TIMage,TMemo)的Canvas属性就是TCanvas对象。在部件上绘制图形就是在部件的画布上绘制。TCanvas的Brush,Pen,Font属性分别是TBrush,TPen,TFont对象,它们用于定义绘制图形的风格。关于TBrush,TPen对象,下节中将详细介绍。

  画布的笔的位置定义在PenPos属性中,可用MoveTo方法来移动笔。如果要在画布上输出文本,可用Textout方法。

  TCanvas有对象很多方法,可完成常用的绘图功能,现将方法及功能简介如表5.1: 

表5.1 TCanvas对象的方法

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

方法名称         形式及说明

───────────────────────────────────────

Arc Arc(x1,y1,x2,y2,x3,y3,x4,y4 : Integer);

Arc方法在椭圆上画一段弧,椭圆由(x1,y1),(x2,y2) 两点所确定的椭圆所决

定。弧的起点是椭圆圆周和椭圆中心与(x3,y3)连线的交点。弧矩形终点是椭

圆圆周和椭圆中心与(x4,y4)连线的交点,以逆时针方向画弧。

Chord Chord(x1,y1,x2,yx,x3,y3,x4,y4 : Integer);

Chord方法连接椭圆上的两点,椭圆由(x1,y1),(x2,y3) 两点所确定的矩形决

定,(x3,y3)是始点,(x4,y4)是终点。

Brushcopy Brushcopy(const Dest : TRect;Bitmap : TBitmap;const Source TRect;

Color : TColor);

         Brushcopy方法把位图的一部分复制到画布的某个矩形区域,并用画笔的当前颜色替换位图的颜色。参数Dest定义画布的一个矩形区域,该矩形用以填充位图,Bitmap定义位图;Source定义位图中的矩形区域,该区域上的位图

      将被复制;Color定义画笔中,用以替换位图的颜色。

CopyRect CopyRect(Dest : TRect;Canvas : TCanvas; Source TRect);

此方法从另一个画布对象上复制部分图像到该画布。Canvas表示源画布,Source是源画布上要复制的图像区域。Dest表示目标画布上将接受复制

图像的矩形区域。

Draw Draw(x,y : Integer;Graphic : TGraphic);

      此方法在画布给定的象素点坐标(x,y)处画Graphic所给的图像,该图像可以是位图,图标或元位图。

Ellips Ellips(x1,y1,x2,y2 : Integer);

      Ellips方法在画布指定的矩形边界上画一个椭圆,(x1,y1)是矩形左上角的象素坐标,x2,y2是矩形右下角的象素坐标。如果矩形形成一个区域,将出现一个椭圆。

LineTo LineTo(x,y : Integer);

LineTo从当前位置画一条线至(x,y)所指定的位置,并把笔的位置移至(x ,y)。

MoveTo MoveTo(x,y : Integer);

MoveTo 将笔的当前位置设置到点(x,y)处,笔的当前位置在PenPos属性中,

改变笔的当前位置使用MoveTo方法,不要设法改变PenPos的值。

Die Die(x1,y1,x2,y2,x3,y3,x4,y4 : Longint);

Die方法绘制椭圆的一部分,椭圆由点(x1,y1),(x2,y2)所指定的矩形所决定,制的那部分由椭圆中心到(x3,y3),(x4,y4)两点的两条辐射线所决定。

Polygon Polygon(Points : array of TPrint);

Polygon方法在画布上绘制一系列的点,各点依次连成线,最后将首尾两点相接形成一个区域,并用当前笔刷填充此区域。

Polyline Polyline(Ports : array of TPort);

Polyline方法在画布上用当前画笔绘制一系列的点,各点依次连成线。

StretchDraw StretchDraw(Const Rect : TRcct : Graphic : TGraphic);

此方法在Rect参数指定的矩形内画一图像。图像延伸改变大小以适应矩形。

Rectangle Rectangle(X1,y1,x2,y2 : Integer);

Rectangle方法在画布上用当前画刷绘制矩形,(x1,y1)是矩形的左上角,(x2,y2)是矩形的右下角。

RomlRect RomlRect((x1,y1,x2,y2,x3,y3, : Integer);

DrawFocuseRect

DrawFocusRect(Const Rect : TRect)

此方法绘制一矩形以指示此矩形获得焦点。此方法是异或(XOR)函数,第二次调用时原有矩形将消失。DrawFocuseRect绘制的矩形不能滚动。要实现滚动功能则先调用此方法使矩形消失,待滚动过后重新绘制。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

5.1.2 Tpen Object(画笔对象) 

        应用程序常用TPen对象在画布上绘制各种线段,笔的颜色在Color属性中定义。线段宽度在Width属性中定义。

  Style属性定义了线段的各种类型,如表5.2: 

表5.2 Styled的取值及含义

━━━━━━━━━━━━━━━━━━━━━━

  Style     含义

──────────────────────

  PSolod 画固定线段

  PSDash 画由下划线组成的线段

  PSDot 画由点组成的线段

  PsDashDot 画点划线

  PsClear 画双点划线

  PsClear 画看不见的线段

  PsInsideFrame 画边界的矩形线框

━━━━━━━━━━━━━━━━━━━━━━━

  Mode属性定义线段的颜色。可结合当前的颜色、屏幕颜色或它们反转值,对线段的颜色重新定义,但不改变Color属性。详见表5.3。 

表5.3 Mode的取值及含义

━━━━━━━━━━━━━━━━━━━━━━━━━━━

 Mode 象素颜色

──────────────────────────

  PmBlack 黑色

  PmWhite 白色

  PmNop 不变

  PmCopy 使用Color属性中的颜色

  PmNotCopy 笔颜色的反转值

  PmMergePenNot 笔的颜色与屏幕颜色反转值的结合

  PmNaskNotPen 屏幕颜色与笔颜色

  PmMergeNotPen 屏幕颜色与笔颜色反转值的结合

━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

5.1.3 TBrush OBject(画刷对象)

  画刷对象用以填充图形,如用画刷颜色或图案对矩形或椭圆进行填充。TBrush拥有一个画刷句柄(HBrush)。

  画刷的颜色定义在Color属性中。画刷还有一个Bitmap属性,该属性只能在运行时得到,画刷可使用位图填充图形以产生特殊效果。位图大小为8个象素点,高8个象素点宽。

  Style属性定义了画刷填充图形的风格。

5.1.4 TColor类型

 

  TColor类型用于定义一个对象的颜色。很多部件的颜色属性就是TColor 类型, 在Graphics单元中TColor定义如下:

 

  TColor = -(COLOR_ENDCOLORS + 1)..$02FFFFF;

 

这是一个32位二进制数据。Graphic单元中还定义了一些常用的颜色常量,这些常量或直接映射成系统调色板中最相近的颜色,或映射成Wondows 控制面板中颜色部分的系统视频颜色。

  直接映射成系统调色板中的颜色有: 

  ClAqua,CLBlack,ClBlue,ClbkGrray,ClFuchsoa......ClYellow

  映射程序用4字节的二进制码来定义颜色,低3 位字节代表RGB 相应的颜色,如$00FF0000表示纯蓝,$0000FF00表示纯绿,$000000FF表示纯红,$00000000表示黑色,$00FFFFFF表示白色。如果最高位字节是$00,则表示用系统调色板中最相近的颜色;最高位字节是$01,则表示用当前调色板中最相近的颜色匹配;最高位字节是$02,则用当前设备描述表中逻辑调色板的次相近颜色匹配。

  用Windows API的SelectPalette函数可创建逻辑调色板,要实现逻辑调色板到硬件调色板的映射,需用函数RealizePalett 

5.2 图形程序的开发 

  Graphex.dpr是一个简单的图形图像应用程序,运行状态如图5.2。该程序可用鼠标绘制多种图形,可设置画笔颜色、画刷填充方式;另外还可以浏览位图、元文件、图标,改变它们的大小,并打印。本节结合例程讲述以下问题:

  ● 在工具条中添加加速按钮

  ● 响应鼠标事件

  ● 设置画笔和画刷

  ● 实现绘图的“橡皮擦”功能

  ● 加入状态条

5.2.1 在工具条中添加加速按钮 

  加速按钮是应用程序常用的部件,它是替代菜单的快捷形式,通常把多个加速按钮集中在一个工具条中以方便使用。Graphex中有三个工具条,它们是TPancel部件,面板上有几组加速按钮,用以设置绘图方式、画笔、画刷。

  每个加速按钮的glyph属性是图像对象,位图对象用来指示该按钮是否被使用。glyph 通常需要四幅图像,分别表示按下、不按、选择、失效四种状态。程序员可根据个人喜好来选择图像。

  加速按钮使用图像来告诉用户其状态和目的,因为按钮上无标题, 因此应给用户正确的提示:

  ● 把Down属性设置成真值使加速按钮呈按下状态

  ● 把Enable属性设置成假值使加速按钮呈失效状态。 

  例程中缺省的绘图工具是画线,因此应用程序开始时画线按钮呈按下状态。

  在应用程序中,常把一些功能相似的按钮放在一起,用户在同一时刻只能选择其中的一个按钮。当其它按钮按下时,原来被按下的按钮自动弹起,这些选择排它的按钮称为一组加速按钮。

  要使多个加速按钮成为一组,将这些按钮的GraphIndex属性设成相同的值。最简单的办法是在设计状态时,用鼠标选中各个加速按钮,然后设置GraphIndex值。

  有时用户按一个已经按下的按钮,希望该按钮能够弹起,这样没有任何按钮被按下,使用AllowAllup 属性可实现上述功能。对于一组加速按钮来讲,设置该组中任一按钮的AllowAllup可使这组的每一个按钮具有这种功能。

Graphex程序中设计了三组加速按钮和两个单独的加速按钮。 第一组加速按钮用来选择绘图工具,它与两个单独的按钮处在同一面板中,这个面板是缺省可见的。另外两个按钮分别代表画笔、画刷。第二组与第三组加速按钮处在两个缺省不可见的面板中,它们分别代表不同风格的画笔和画刷,只有按下第一个面板中的画笔(或画刷)按钮,第二(或第三) 个面板才会显示,这样用户就可以选择画笔、画刷了。 

5.2.2 响应鼠标事件 

  鼠标常被用作绘图的工具,应用程序利用鼠标位置的变化来绘制各种不同的图形。鼠标有三个动作:鼠标按钮按下、鼠标移动、鼠标按钮弹起。在Delphi中, 对应三个动作有三个不同的事件:OnMouseDown,OnMouseMove,OnMouseUp。

当Dlephi应用程序探测到一个鼠标动作时,它传递五个参数,并调用相应的事件响应。

程序员可利用这些参数来定义事件程序。五个参数如下表5.4: 

表5.4 鼠标事件的五个参数

━━━━━━━━━━━━━━━━━━━━━━━━━━━

  参数      含义

──────────────────────────

  Sender 探测鼠标动作的对象

  Button 涉及的鼠标按钮:左键,中键,右键

  Shift 鼠标动作时,Alt,Ctrl,Shift按钮的状态

  X,Y 事件发生时鼠标的坐标

━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  当鼠标按下时发生OnMouseDown事件。举一个简单例子来说明程序如何对该事件进

行响应。假如我们想在鼠标按下的地方出现"Here"。

响应鼠标的OnMouseDown事件 

  可在该事件中调用TextOut方法: 

  procedure TForm1.FormMouseDown(Sender: TObject,Button: TMouseButton;

Shift : TShifState; X,Y : Integer);

begin

Canvas.TextOut(X, Y, 'Here!');

end; 

用户放松鼠标键时发生OnMouseUp事件。该事件发生时,鼠标到达的对象并不一定是鼠标键按下时鼠标所在的对象。例如,用户可在窗体之外画一条线段,(鼠标在窗体外,线段在窗体内)。下面的代码可用鼠标绘制直线:

procedure TForm1.FormMouseDown(Sender:TObject)

begin

Moveto(x,y);

end;

 

procedure TForm1.FormMouse Up(Sender:Tobject)

begin

Lineto(X, Y);

end;


想死你们了!

TOP

DELPHI基础教程

第五章 Delphi图形图像编程(二)

       画直线时,用户只有在松开鼠标才能看见直线,对直线的变化不能进行实时观测。这是因为鼠标移动时程序没有进行某种应。Delphi定义了OnMouseMove事件来响应鼠标移动。以下代码可使用户随时观测直线的变化: 

  procedure TForm1.FormMouseMove(Sender:Tobject)

begin

Drowto(X,Y);

Moveto(origin);

end. 

origin是起始点。

5.2.3 绘图功能的实现

  绘图软件常根据用户的要求改变绘图工具。Graphex.dpr例程中,当用户按下某个按钮时,可选择绘图工具中的画笔或画刷,在程序类型说明部分定义了五种绘图工具。

   type

TDrawingTool = (dtLine,dtRectangle,dtEllips,dtRoundRect,dtPolygon); 

当选中某种按钮,则选中了相应的绘图工具,如: 

procedure TForm1.LineButtonClick(Sender: TObject);

begin

DrawingTool := dtLine;

end; 

procedure TForm1.RectangleButtonClick(Sender: TObject);

begin

DrawingTool := dtRectangle;

end; 

procedure TForm1.EllipseButtonClick(Sender: TObject);

begin

DrawingTool := dtEllipse;

end; 

procedure TForm1.RoundRectButtonClick(Sender: TObject);

begin

DrawingTool := dtRoundRect;

end; 

procedure TForm1.PolygonButtonClick(Sender: TObject);

begin

DrawingTool :=dtPolygon;

end;  

DrawShape过程定义了每种绘图工具的动作: 

procedure TForm1.DrawShape(TopLeft, BottomRight: TPoint; AMode: TPenMode);

begin

with Image.Canvas do

begin

Pen.Mode := AMode;

case DrawingTool of

dtLine: begin

MoveTo(TopLeft.X, TopLeft.Y);

LineTo(BottomRight.X, BottomRight.Y);

end;

dtRectangle: Rectangle(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y);

dtEllipse: Ellipse(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y);

dtRoundRect: RoundRect(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y,

(TopLeft.X - BottomRight.X) div 2, (TopLeft.Y - BottomRight.Y) div 2);

dtPolygon:Polygon([Point(0,0),TopLeft,BottomRight]); end;

end;

end; 

 程序刚运行时,只有一个工具栏。当用户单击画笔和画刷时,则出现相应的工具栏,如图5.4。其代码如下: 

procedure TForm1.PenButtonClick(Sender: TObject);

begin

PenBar.Visible := PenButton.Down;

end; 

procedure TForm1.BrushButtonClick(Sender: TObject);

begin

BrushBar.Visible := BrushButton.Down;

end;

         在设计绘图程序时,还要解决一些问题。如为了在鼠标移动时能观测图形的变化,我们定义了OnMouseMove事件。但会出现这样的现象,当鼠标进入绘图区时,用户未按下鼠标键,画布上却出现绘制的图形,这是我们不希望看到的。其原因是没有对鼠标按钮是否按下进行判断。因此在窗体对象中定义了drawing的域,当鼠标按钮按下时,drawing 设置成真值。只有drawing为真,鼠标移动才执行绘图功能;当鼠标键松开时,drawing设置成假,鼠标移动将不执行绘图动作。

       另外一个问题是, 我们希望得到的是鼠标按钮按下和松开这两点所形成的图形,但OnMouseMove却把鼠标轨迹上各点与起始点所形成的所有图形画在屏幕上,这同样是我们不希望看到的,为了解决这些问题,程序定义了鼠标的三个事件: 

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

Drawing := True;

Image.Canvas.MoveTo(X, Y);

Origin := Point(X, Y);

MovePt := Origin;

OriginPanel.Caption := Format('Origin: (%d, %d)', [X, Y]);

end; 

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

if Drawing then

DrawShape(Origin, Point(X, Y), pmCopy);

Drawing := False;

end; 

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,

Y: Integer);

begin

if Drawing then

begin

DrawShape(Origin, MovePt, pmNotXor);

MovePt := Point(X, Y);

DrawShape(Origin, MovePt, pmNotXor);

end;  

MovePt用来记录鼠标当前位置。当下次鼠标移动时, 就能在上次鼠标绘制的图形上画一个形状、大小一样的图形,并把画笔颜色设置成PmNotXor,使上次绘制的图形颜色变成了屏幕颜色,从而达到“橡皮擦”的效果。

  将画笔、画刷的Style属性设置成用户希望的值,可实现对画笔和画刷风格的选择。 

procedure TForm1.SetBrushStyle(Sender: TObject);

begin

with Image.Canvas.Brush do

begin

if Sender = SolidBrush then Style := bsSolid

else if Sender = ClearBrush then Style := bsClear

else if Sender = HorizontalBrush then Style := bsHorizontal

else if Sender = VerticalBrush then Style := bsVertical

else if Sender = FDiagonalBrush then Style := bsFDiagonal

else if Sender = BDiagonalBrush then Style := bsBDiagonal

else if Sender = CrossBrush then Style := bsCross

else if Sender = DiagCrossBrush then Style := bsDiagCross;

end; 

procedure TForm1.SetPenStyle(Sender: TObject);

begin

with Image.Canvas.Pen do

begin

if Sender = SolidPen then Style := psSolid

else if Sender = DashPen then Style := psDash

else if Sender = DotPen then Style := psDot

else if Sender = DashDotPen then Style := psDashDot

else if Sender = DashDotDotPen then Style := psDashDotDot

else if Sender = ClearPen then Style := psClear;

end;

end; 

5.3 图像对象概述 

5.3.1 TGraphic对象

  TGraphic对象是TBitmap ,TIcon,Tmetafile对象的基类。如果知道图像的具体类型( 如位图, 图标元文件) , 则应将图像贮存在相应类型的对象中( 如TBitmap,TIcon,Tmetafile),否则应该使用可贮存任何图像类型的TPicture对象。 

5.3.2 TPicture对象 

  TPicture对象可以保存位图、图标或元文件。Graphic属性中包括图像的类型;图像的高度和宽度分别定义在Height,Width属性中;调用LoadFromFile方法,可以从文件中装载一幅图像:

procedure TForm1.FormCreate(Sender: TObject);

begin

BitBtn1.Glyph.LoadFromFile('TARTAN.BMP');

end; 

       要保存一个位图,则要用SaveToFile方法;要把图像复制到剪切板,可以调用TClipboard对象的Assign方法。 

5.3.3 TImage部件 

  TImage部件用以在窗体中显示图像,它的Picture 属性保存着要显示的图像, 这是一个TPicture对象。AutoSize,Stretch属性是用来调节部件与图像的大小的。当AutoSize 为真值时,TImage部件将根据它所包含的图像的大小来调整自身的大小;当AutoSize为假值时,不论图像有多大,部件将保持设计时的大小。如果部件比图像小, 那么只有一部分图像是可见的。当Stretch为真值时,位图像将根据部件的大小调整自身的大小,当部件大小改变时,元文件也做相应变化。Stretch属性对图标没有作用。 

5.3.4 TBitmap Object(位图对象)

  位图对象包含一个位图图像,有HBITMAP,HPALETE句柄,可自动管理调色板。位图对象也有画布属性。位图的Palette属性用来控制位图的颜色映射,它包括256种可显示的颜色。 如果应用程序用前景色绘制位图,Palette 属性的颜色将被加入Windows系统调色板,其它颜色被映射到系统调色板已存在的颜色。如果应用程序用自己的颜色绘制位图,而其它程序已占有系统调色板,位图的颜色将被映射到系统调色板中。

  如果Monochrome属性设置成假,位图将显示成彩色,反之显示成黑白色。

  调用Draw和StretchDraw方法可在画布上绘制位图。 

5.4 图像对象的应用 

  本章例程中,单击(文件|浏览)菜单项,将弹出一个图像浏览窗体。如果用户在窗体中选择文件列表框的图形文件,窗体右上角的图像部件上将出现此文件所代表的图像;若选择“雕刻效果”按钮中检查框,窗体中的加速按钮和位图按钮上将出现位图。

  以下代码是将图像文件装载至图像部件上: 

procedure TImageForm.FileListBox1Click(Sender: TObject);

var

FileExt: string[4];

begin

FileExt := UpperCase(ExtractFileExt(FileListBox1.Filename));

if (FileExt = '.BMP') or (FileExt = '.ICO') or (FileExt = '.WMF') then

begin

Image1.Picture.LoadFromFile(FileListBox1.Filename);

Label1.Caption := ExtractFilename(FileListBox1.Filename);

if (FileExt = '.BMP') then

begin

Label1.Caption := Label1.Caption +

Format(' (%d x %d)', [Image1.Picture.Height, Image1.Picture.Width]);

ViewForm.Image1.Picture.Bitmap := Image1.Picture.Bitmap;

ViewAsGlyph(FileExt);

end;

if FileExt = '.ICO' then Icon := Image1.Picture.Icon;

if FileExt = '.WMF' then

ViewForm.Image1.Picture.Metafile := Image1.Picture.Metafile;

end;

end;   

这个过程首先判断文件类型,如果是图像文件,则将图像装载至图像部件上,并在标签上列出文件名称。如果是位图文件,还将显示其大小。

  在加速按钮和位图按钮中显示位图的代码如下: 

  procedure TImageForm.CheckBox1Click(Sender: TObject);

begin

ViewAsGlyph(UpperCase(ExtractFileExt(FileListBox1.Filename)));

end; 

procedure TImageForm.ViewAsGlyph(const FileExt: string);

begin

if CheckBox1.Checked and (FileExt = '.BMP') then

begin

SpeedButton1.Glyph := Image1.Picture.Bitmap;

SpeedButton2.Glyph := Image1.Picture.Bitmap;

SpinEdit1.Value := SpeedButton1.NumGlyphs;

BitBtn1.Glyph := Image1.Picture.Bitmap;

BitBtn2.Glyph := Image1.Picture.Bitmap;

end;

end; 

         窗体中有一个检查框用来检验图像部件的Strecth 属性的效果。当此检查框被选中时,Stretch设成真值,图像将根据部件大小调整自身大小。代码如下: 

procedure TImageForm.StretchCheckClick(Sender: TObject);

begin

Image1.Stretch := StretchCheck.Checked;

end;  

             在这个窗体中,用户可以在屏幕和打印纸上调整图像部件的大小、位置。调整图像的代码如下:  

procedure TViewForm.SpinEdit1Change(Sender: TObject);

begin

IMage1.Height:=105+SpinEdit1.Value*5;

IMage1.Width:=105+SpinEdit1.Value*5;

end; 

procedure TViewForm.SpinEdit2Change(Sender: TObject);

begin

Image1.Left:=40+ SpinEdit2.Value*20;

end; 

procedure TViewForm.SpinEdit3Change(Sender: TObject);

begin

Image1.Top:=96+SpinEdit3.Value*10;

         当用户按下标有“全尺寸”字样的按钮时,另一个窗体将显示。

图像打印代码如下:

procedure TViewForm.Button1Click(Sender: TObject);

begin

Printer.BeginDoc;

Printer.Canvas.Draw(Trunc(1.5*Image1.Left),Trunc(1.5*Image1.Top), Image1.Picture.Graphic);

Printer.EndDoc;

end;


想死你们了!

TOP

DELPHI基础教程

第六章 文件管理(一)

         文件是同一类型元素的有序集合,是内存与外设间传输数据的渠道。一些外设如显示器、键盘、打印机等都可以看作文件,但最常用的还是磁盘文件,这也是本章我们主要讨论的对象。

         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。前者大部分以文件变量为操作对象,而后者大部分以文件名或文件句柄为操作对象。这里为了方便读者的使用,我们按功能重新进行了分类。在下一节中主要应用I/O Routine主题下的过程,而在第四节的综合举例中主要应用File_Management Routine主题下的过程。

       另外,Windows提供了许多有关文件管理的API函数。虽然在一般情况下,利用Delphi提供的函数已足够解决问题,但有时候仍然需要使用Windows API。在(6.4.4.2)中我们就用到了Windows API函数GetDriveType。有关Windows API函数的情况,请读者参阅相关的资料,这里不再进行介绍。

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;  


想死你们了!

TOP

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 = 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.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;


想死你们了!

TOP

DELPHI基础教程

第六章 文件管理(三)

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单元中。 

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类型的对话框,显示文件的属性

当用户修改并确认后程序重新设置文件属性。

  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事件处理过程中实现的。 

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,类名改为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[1] = drive then

begin

DriveTabSet.TabIndex := i;

Exit;

end;

end;

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程序中不可避免的要涉及到的问题。本章介绍的思路和方法将为读者成为一个熟练的程序员奠定基础。


想死你们了!

TOP

DELPHI基础教程

第七章 剪贴板和动态数据交换(一)


--------------------------------------------------------------------------------

         应用程序间的数据交换是象Windows 这样的多任务环境的重要特性。作为一种基于Windows的开发工具,Delphi支持如下四种数据交换方式:剪贴板、动态数据交换 ( DDE)、对象联接与嵌入(OLE)以及动态联接库(DLLs)。这中间前三种方式最为常用,OLE功能最为强大,DDE次之。而剪贴板使用最为方便。在本章,我们只讨论剪贴板和动态数据交换。利用OLE实现数据交换见下一章,利用动态联接库(DLLs)进行数据交换将在第十章中介绍。  

7.1 剪贴板及其应用 

         本质上,剪贴板只是一个全局内存块。当一个应用程序将数据传送给剪贴板后,通过修改内存块分配标志,把相关内存块的所有权从应用程序移交给Windows自身。其它应用程序可以通过一个句柄找到这个内存块,从而能够从内存块中读取数据。这样就实现了数据在不同应用程序间的传输。  

        剪贴板虽然功能较为简单,且不能实现实时传输,但却是更为复杂的DDE和OLE的基础。对于一些只是偶尔需要使用其它应用程序数据的程序来说,使用剪贴板不失为一种方便、快捷的方式。

         Delphi把剪贴板的大部分功能封装到一个TClipboard类中,同时把使用频度最高的文本传输功能(包括DBImage的图像传输功能)置入相应部件作为部件的方法,从而使用户可以十分方便地使用剪贴板进行编程。 

7.1.1 使用剪贴板传输文本 

剪贴板传输文本主要是应用如下的三个方法:CopyToClipboard、CutToClipboard 和PasteFromClipboard。包含这些方法的部件如下表所示。 

   表7.1 包含剪贴板方法的部件

━━━━━━━━━━━━━━━━━━━━━━━━━━━

方 法 部 件

———————————————————————————

TDBEdit TDBMemo

TDBImage

CopyToClipboard TEdit TMemo TMaskEdit

TOLEContainer

TDDEServerItem

———————————————————————————

TDBEdit TDBMemo

CutToClipboard TDBImage

TEdit TMemo TMaskEdit

———————————————————————————

TDBEdit TDBMemo

PasteFromClipboard TDBImage

TEdit TMemo TMaskEdit

━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

                  除TDBImage外,其余全是有关文本的控件。

        在把文本传输到剪贴板之前,文本必须被选中。

          若选TMaskEdit的AutoSelect属性为True,则当MaskEdit获得输入焦点时文本自动被选中;若选TEdit、TMemo的HideSelection属性为True,则失去焦点时,文本选中状态自动隐藏,重新获得焦点时再显示。

下面的语句把MaskEdit中选中的文本剪切到剪贴板: 

MaskEdit .CutToClipboard; 

下面的语句把剪贴板中的文本粘贴到Memo的当前光标处: 

Memo.PasteFromClipboard; 

利用剪贴板类也可以实现文本的传输,见(7.1.2)中的介绍。 

7.1.2 剪贴板类 

       为方便剪贴板的操作,Delphi在Clipbrd库单元中定义了一个TClipboard类,并且预定义了一个变量Clipboard作为类TClipboard的实例,从而使用户在绝大多数场合不必自己去定义一个TClipboard的实例。

        利用剪贴板类可以进行文本、图像和部件的传输,剪贴板类为实现这些方法提供了相应的属性和方法。表7.2、表7.3列出了TClipboard属性和方法的意义。  

表 7.2 TClipboard的属性

━━━━━━━━━━━━━━━━━━━━━━━━━━━

属 性 意 义

───────────────────────────

AsText 保存剪贴板的文本,只有运行时才可设置

FormatCount 可用剪贴板格式的数目

Formats 可用剪贴板格式链

━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

   表 7.3 TClipboard的方法

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

方 法 参 数 意 义

─────────────────────────────────────

Clear 无 清除剪贴板的内容

Assign Source:TPersistent 把Source参数指定的对象拷贝到剪贴板,常

用于图形、图像对象

Open 无 打开剪贴板,阻止其它应用程序改变它的内容

Close 无 关闭打开的剪贴板

SetComponent Source:TPersistent 把部件拷贝到剪贴板

GetComponent Owner 从剪贴板取回一个部件并放置

Parent :TPersistent

SetAsHandle Format:Word 把指定格式数据的句柄交给剪贴板

返回类型:THandle

GetAsHandle Format:Word 返回剪贴板指定格式数据的句柄

返回类型:THandle

HasFormat Format:Word 判断剪贴板是否拥有给定的格式

返回类型:Boolean

SetTextBuf Buffer:PChar 设置剪贴板的文本内容

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

  剪贴板中可能的数据格式如下表。 

表 7.4 剪贴板数据格式及其意义

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

数据格式 意 义

──────────────────────────────

CF_TEXT 文本。每行以CF_LF结束,nil标志文本结束

CF_BITMAP Windows位图

CF_METAFILE Windows元文件

CF_PICTURE TPicture类型的对象

CF_OBJECT 任何TPersistent类型的对象

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 

利用TClipboard实现文本的传输使用AsText属性和SetTextBuf方法。

AsText属性为非控件部件的剪贴板操作提供了方便。如: 

Clipboard. AsText := Form1.Caption ; 

把Form1的标题拷贝到剪贴板。 

Label1.Caption := Clipboard.AsText; 

把剪贴板中的文本写入Label1。

SetTextBuf用于把超过255个字符的字符串拷入剪贴板。 

7.1.3 利用剪贴板传输图像 

7.1.3.1 拷贝 

Image部件上的内容和窗体上的图形可以直接拷贝到剪贴板。图像拷贝利用Clipboard的Assign方法。

例如: 

Clipboard.Assign(Image1.Picture); 

把Image1上的图像拷贝到剪贴板。 

7.1.3.2 剪切 

图像的剪切是首先把图像拷贝到剪贴板,而后在原位置用空白图像进行覆盖。

下面一段程序表示了图像的剪切。  

procedure TForm1.Cut1Click(Sender: TObject);

var

ARect: TRect;

begin

Clipboard.Assign(Image1.Picture);

with Image.Canvas do

begin

CopyMode := cmWhiteness;

ARect := Rect(0, 0, Image.Width, Image.Height);

CopyRect(ARect, Image.Canvas, ARect);

CopyMode := cmSrcCopy;

end;

end; 

7.1.3.3 粘贴 

从剪贴板上粘贴图像,首先检测剪贴板上的数据格式。如果格式为CF_BITMAP,则调用目标位图的Assign 方法粘贴图像。

程序清单如下。

procedure TForm1.PasteButtonClick(Sender: TObject);

var

Bitmap: TBitmap;

begin

if Clipboard.HasFormat(CF_BITMAP) then

begin

Bitmap := TBitmap.Create;

try

Bitmap.Assign(Clipboard);

Image.Canvas.Draw(0, 0, Bitmap);

finally

Bitmap.Free;

end;

end;

end; 

try...finally为资源保护块,参第十二章。

7.1.4 建立自己的剪贴板观察程序 

      在这一节中我们要建立一个自己的剪贴板观察程序,用来保存截获到剪贴板中的位图。

       Windows允许用户建立自己的剪贴板观察程序,并把该程序添加到一个剪贴板观察器链中。在链中,位置靠前的程序有义务把有关剪贴板的消息传递到紧随其后的观察程序。而处于链首的程序由Windows的消息循环机制直接把剪贴板消息发送过来。

       建立一个剪贴板观察程序,首先该程序必须能响应相应的Windows消息。对于那些熟悉Microsoft公司Visual Basic的读者来说,这是令他们头疼而束手无策的地方。但Delphi在这方面却有良好的表现:利用关键字message,用户可以将一个过程定义为响应特定的Windows消息。如: 

procedure WMDrawClipboard(var Msg:TWMDrawClipboard);

message WM_DRAWCLIPBOARD; 

             可以响应WM_DRAWCLIPBOARD消息。类TWMDrawClipboard是消息类Message 的子类。Delphi把所有的消息都重新进行了定义,使用户在使用时可以直接引用其便于记忆的数据成员,而不必再自己动手去分解消息。虽然这并不能算作是一个重大的改进,但却体现了Delphi处处为用户方便着想的特点。

               我们将要建立的程序目的是把截获到剪贴板上的位图保存下来。在本书的写作过程中,这一工作是大量存在的。虽然利用Windows工具PaintBrush(画笔),通过粘贴、保存等操作可以实现这一功能,但却存在以下一些问题:

1.程序频繁切换影响效率,当有大量位图存在时更是如此;

2.画笔有一个很讨厌的缺陷:当剪贴板上的位图比画笔界面的客户区大时,客户区外的位图被截断。因而往往需要根据所截获位图的大小来调整画笔客户区的大小,并重新进行粘贴。而如果开始就把画笔客户区调整到足够大,又会覆盖掉屏幕上一些有用的信息。

          为解决这些问题,我开发了下面的程序。程序启动时,以极小化方式运行。此时只要剪贴板中存入位图,则自动弹出一个对话框请求用户保存。如果用户希望查看确认,则可以双击运行程序图标,选择相应按钮,剪贴板中的位图就会显示在屏幕上。

部件关键属性设计如下: 

ClipSaveForm:

Caption=‘Save Bitmap in Clipboard '

Panel1:

Align = ' Top '

Image1:

Align = ' Client '

SaveDialog1:

FileEditStyle = fsEdit

FileName = '*.bmp'

Filter = 'Bitmap Files(*.bmp)|*.bmp|Any Files(*.*)|*.*'

InitialDir = 'c:\bmp'

Title = 'Save Bitmap' 

         程序主窗口是TForm派生类TClipSaveForm的实例。TClipSaveForm通过定义一些私有数据成员和过程,使响应和处理Windows的相应消息成为可能。下面是TClipSaveForm的类定义: 

type

TClipSaveForm = class(TForm)

SaveDialog1: TSaveDialog;

Image1: TImage;

Panel1: TPanel;

Button1: TButton;

SpeedButton1: TSpeedButton;

SpeedButton2: TSpeedButton;

Button2: TButton;

procedure FormCreate(Sender: TObject);

procedure FormDestroy(Sender: TObject);

procedure Button1Click(Sender: TObject);

procedure Button2Click(Sender: TObject);

procedure SpeedButton1Click(Sender: TObject);

procedure SpeedButton2Click(Sender: TObject);

private

{ Private declarations }

MyBitmap: TBitmap; { 保存截获的位图 }

View: Boolean; { 判断是否显示 }

NextViewerHandle: HWND; { 下一剪贴板观察器的句柄 }

procedure WMDrawClipboard(var Msg:TWMDrawClipboard);

message WM_DRAWCLIPBOARD;

procedure WMChangeCBChain(var Msg:TWMChangeCBChain);

message WM_CHANGECBCHAIN;

{ 响应Windows的剪贴板消息 }

public

{ Public declarations }

end;

        窗口创建时,把该窗口登录为剪贴板观察器,添加到剪贴板观察器链中,同时进行变量、部件和剪贴板的初始化。 

procedure TClipSaveForm.FormCreate(Sender: TObject);

begin

View := False;

SpeedButton2.Down := True;

MyBitmap := TBitmap.create;

try

MyBitmap.Width := 0;

MyBitmap.Height := 0 ;

except

Application.terminate;

end;

Clipboard.Clear;

NextViewerHandle := SetClipboardViewer(Handle);

end; 

窗口关闭时,退出剪贴板观察器链,并释放内存: 

procedure TClipSaveForm.FormDestroy(Sender: TObject);

begin

ChangeClipboardChain(Handle,NextViewerHandle);

MyBitmap.Free;

end; 

在以上两段程序中用到的两个Windows API函数SetClipboardViewer和ChangeClipboardChain分别用于登录和退出剪贴板观察器链。

程序保存位图的功能是在消息响应过程WMDrawClipboard中实现的。该过程在剪贴板内容有变化时被调用。 

procedure TClipSaveForm.WMDrawClipboard(var Msg: TWMDrawClipboard);

var

FileName: String;

begin

If NextViewerHandle <> 0 then

SendMessage(NextViewerHandle,msg.Msg,0,0);

If ClipBoard.HasFormat(CF_BITMAP) then

begin

MyBitmap.Assign(Clipboard);

If SaveDialog1.Execute then

begin

FileName := SaveDialog1.FileName;

MyBitmap.SaveToFile(FileName);

end;

If View then

begin

WindowState := wsNormal;

Image1.Picture.Bitmap := MyBitmap;

end;

end;

Msg.Result := 0;

end; 

         程序首先判断在剪贴板观察器链中是否还存在下一个观察器。如果有,则把消息传递下去,这是剪贴板观察器程序的义务。而后判断剪贴板上内容的格式是否为位图。如是,则首先把剪贴板上内容保存到数据成员MyBitmap中,并激活一个文件保存对话框把位图保存到文件中。如果View=True,则把窗口状态(WindowState)设置为wsNormal,并把MyBitmap赋给Image部件的相应值,使用户可以对剪贴板上的位图进行观察。

         消息响应过程WMChangeCBChain在剪贴板观察器链上其它观察器退出时被调用。根据被移出观察器的不同位置决定了不同的处理方法。

procedure TClipSaveForm.WMChangeCBChain(var Msg: TWMChangeCBChain);

begin

if Msg.Remove = NextViewerHandle then

NextViewerHandle := Msg.Next

else

if NextViewerHandle <> 0 then

SendMessage(NextViewerHandle,Msg.Msg,Msg.Remove,Msg.Next);

Msg.Result := 0;

end;

窗口上有两个加速按钮,两个按钮。它们击键(click)事件处理过程如下。每一程序段的意义是非常显然的。 

procedure TClipSaveForm.Button1Click(Sender: TObject);

begin

Close;

end;

procedure TClipSaveForm.Button2Click(Sender: TObject);

begin

WindowState := wsMinimized;

end;

procedure TClipSaveForm.SpeedButton1Click(Sender: TObject);

begin

View := True;

Image1.Picture.Bitmap := MyBitmap;

end; 

procedure TClipSaveForm.SpeedButton2Click(Sender: TObject);

begin

View := False;

Image1.Picture.Bitmap := nil;

end; 

通过对这个程序的介绍,以下几点是应该注意的:

1.提供了一种自己截获和处理剪贴板上内容的方法。读者可以根据需要进一步扩充;

2.提供了响应Windows消息的方法。在第三篇有关自定义部件开发的内容中,这一问题还要详细论述;

3.最后的一点启示是:在Delphi程序开发中巧妙应用传统的Windows方法(如消息处理、 API函数等)仍是很有必要的。而在应用这些方法中所体现的方便之处,正是Delphi胜过其它可视化开发工具的一个重要方面。 

7.2 Windows的DDE原理和 Dephi的DDE实现机制 

7.2.1 Windows的DDE原理 

        Windows的DDE机制基于Windows的消息机制。两个Windows应用程序通过相互之间传递DDE消息进行DDE会话(Conversation),从而完成数据的请求、应答、传输。这两个应用程序分别称为服务器(Server)和客户(Client)。服务器是数据的提供者,客户是数据的请求和接受者。

        DDE会话由客户程序启动。客户程序把一条消息(WM_DDE_INITIATE)传播给当前运行的所有Windows程序。这条消息指明了客户程序所需要的一般数据(应用程序、主题)。拥有这些数据的DDE服务器可以响应这条被传播的消息。此时,DDE会话就开始了。

        由于在每个主题中,DDE服务器可以支持一个或多个数据项,所以在客户请求数据时应同时指明应用程序名、主题名和项目名。应用程序、主题、项目是DDE中三个最基本的概念。

        利用Windows本身提供的DDE消息和API进行DDE编程是一件相当棘手的问题。 虽然使用DDE管理库(ddeml.dll)可以一定程度上减轻开发者的工作负担,但开发DDE程序仍不是一件轻松的事情。

         此时Delphi出现了!Delphi通过其自身巧妙的设计使开发一个DDE应用程序同开发一个普通程序一样地快捷、方便。 

7.2.2 Delphi的DDE实现机制简介 

Delphi把所有的DDE功能做到四个部件中,它们是:

● TDDEClientConv : 用于客户程序建立和维护一个DDE会话

● TDDEClientItem : 用于客户程序建立和维护数据交换通道

● TDDEServerConv : 用于服务器程序响应DDE会话

● TDDEServerItem : 用于服务器程序维护数据交换通道 

  前两个部件用于生成一个DDE客户程序,后两个部件用于生成一个DDE服务器程序。如果一个应用程序同时拥有这些部件,则这一程序既可以充当DDE客户,也可以充当DDE服务器。

        会话部件TDDEClientConv、TDDEServerConv用于建立和维护一个DDE会话。DDE会话包括DDE服务和DDE主题两部分。

        DDE服务是DDE服务器的名称,即在一般的Windows DDE机制中所讲的应用程序名。一般说来这一名称是DDE服务器应用程序执行文件名去掉 .EXE后缀。比如你的应用程序要和Word 6.0建立会话,则DDE服务为WINWORD。但也不尽然。比如你的应用程序要和Borland ReportSmith ( RPTSMITH.EXE ) 建立会话,则DDE 服务为 Report Smith。DDE服务到底如何,读者可参看相关的DDE服务器应用程序文档。

        DDE主题是一个包含了联接信息的数据单元。一般说来DDE 主题是一个包括扩展名的完整文件名。例如和Excel中的一个文件建立DDE会话,则主题可能是 

Topic = 'c:\excel\Example\sale.xls' 

        如果服务器是一个Delphi应用程序,缺省情况下主题是包含欲联接数据窗体的标题。如果服务器使用了DDEServerConv部件,则要求使用部件DDEServerConv的名称作为DDE主题。

        项目部件TDDEclientItem、TDDEServerItem用于建立和维护DDE数据的传输通道。 DDE项目中包含着实际欲传输的数据。DDE项目的格式取决于DDE服务器应用程序。一个可能的DDE项目例子是电子表格中的单元和数据库表中的域。如果服务器是Delphi应用程序,则项目是连接的 DDEServerItem部件的名称。

        Delphi的DDE实现机制方便、实用,但也有一个令人遗憾的缺陷:只能传输文本数据以及命令、宏,而不能传输图像数据。在这一点上微软公司推出的Visual Basic 要略胜一筹。不过在目前文本数据的使用仍是最广泛的,而且图像传输可以利用剪贴板和OLE来实现,则这一缺陷也并无很大的影响 

7.3 DDE客户程序的实现

        DDE客户程序启动DDE会话,向服务器请求并从服务器接收数据。同时还可以向服务器发送数据、命令、宏,改变服务器的状态并控制服务器的运行。 

7.3.1 联接模式(ConnectMode)

  Delphi的DDE提供了两种联接模式:自动和人工。这可以通过DDEClinetConv 部件的ConnectMode属性进行设置。如下表所示。 

表 7.5 DDE的联接模式

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

     值           意      义

───────────────────────────────

   ddeAutomatic 在运行中当包含TDDEClientConv部件的窗口创建时

联接自动建立

   ddeManual 只有当调用OpenLink方法时联接才建立

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━   

不同联接模式,DDE客户程序的实现方式不同。

对于自动模式:

1. 向窗体中加入DDEClientConv和DDEClientItem部件并命名;

2. 把DDEClientItem部件的DdeConv属性设置为DDEClientConv部件的名称;

如果在设计时建立,则通过对象观察器进行选择;如果在运行时建立联系, 则通过如下的一条语句设置属性的值: 

DDEClientItem1.DdeConv := 'DDEClientConv1' ; 

3. 和服务器建立联系,实现数据共享。

对于人工模式:

1.向窗体中加入DDEClientConv部件;

2.和服务器建立联系;

3. 数据更新时调用RequestData方法申请并获得数据。 

7.3.2 和DDE服务器建立联系 

          和DDE服务器建立联系,既可以在设计时进行,也可以在运行时进行。

          在设计时,DDE联接可以通过剪贴板进行粘贴。具体步骤如下:

1. 激活服务器程序,并选中你的客户程序欲联接的数据;

2. 把数据和DDE联接信息拷贝到剪贴板上。一般说来这只需要选择服务器应用程序的 Edit|Copy 菜单;

3. 在Delphi IDE的设计窗体中选中DDEClientConv部件;

4. 在Object Inspector(对象观察器)中单击DDEService属性或DDETopic属性,然后再单击Ellipsis按钮,打开DDE Info对话框;

5.选择Paste Link按钮。此时App编辑框和Topic编辑框被自动填充。如果Paste Link按钮变灰,说明你准备用作服务器的应用程序不支持DDE或者DDE信息没有被成功地拷贝到剪贴板上;

6.选择OK 按钮。此时Object Inspector中的DDEService、DDETopic 属性包含了建立一个DDE联接的正确值。

对于人工模式以下步骤是不需要的。

7.选中DDEClientItem部件,并在Object Inspector中设置DdeConv属性为已完成联接的DDEClientConv部件名称;

8.假如剪贴板上的DDE 联接信息仍保留的话,从Object Inspector的下拉列表框中选择 DDEItem 属性的值。否则输入正确的值。

在运行时,调用 SetLink 方法来建立DDE联接。

         SetLink有两个String类型的参数,分别用来接受DDEService和DDETopic的值。过程执行后DDEClientConv部件的DDEService 和DDeTopic属性被设置。要注意的是:在运行时直接设置DDEService和DDETopic的值并不能建立一个DDE联接,而必须调用SetLink 方法进行初始化。

        比如,下面的语句和Excel的System主题建立联接: 

DDEClietnConv. SetLink('Excel','System'); 

        调用SetLink方法后,还需要设置DDEClientItem部件的DDEItem属性。

        比如,下面的语句联接Excel的Topics项目,用以获取当前活跃文件的文件名: 

DDEClietnItem.DDEItem := 'Topics'; 

        当DDE联接建立后,联接的数据保存在DDEClientItem部件的Text和Lines 属性中,Text用于保存一个字符串(String),Lines用于保存一个字符串链表(TStrings)对象。

        为了显示联接数据,可以在DDEClientItem的OnChange事件中把数值赋给一个可视部件。

下面的事件过程把联接数据实时地显示在一个编辑框中。

  procedure Form1.DDEClientItemChange(Sender: Tobject);

begin

Edit1.Text := DDEClientItem1.Text;

end; 

      运行状态下也可以从剪贴板上粘贴DDE联接信息,并调用SetLink建立DDE会话。下面的例子显示了当用户按下应用程序中的Paste Link按钮时,动态建立DDE会话的过程。 

procedure Form1.OnPasteLink(Sender: Tobject);

  var

Service, Topic, Item: String;

begin

if GetPasteLinkInfo (Service, Topic, Item) then

begin

AppName.Text := Service;

TopicName.Text := Topic;

ItemName.Text := Item;

DDEClient.SetLink (Service, Topic);

DDEClientItem.DdeConv := DDEClient;

DDEClientItem.DDEItem := ItemName.Text;

end;

end; 

        GetPasteLinkInfo是DDEMan 库单元中定义的一个过程。如果返回True,则DDE联接信息保存在三个参数中;如果返回False,说明剪贴板上没有正确格式的DDE联接信息。 

7.3.3 数据申请 

虽然自动模式快捷、方便,但仍有一些理由使用DDE的人工模式:

1.服务器程序可能不支持自动数据传输,客户必须显式申请服务器更新一个特定的项目;

2.节省通信费用。假如没有实时传输的要求,则人工模式可以大幅度降低通信的开销;

3.若客户程序只用于控制服务器的运行,则往往没有必要使用自动模式。

        人工模式下客户程序的数据更新需要采用数据申请的方式。数据申请需要调用DDEClientConv部件的RequestData方法。RequestData有一个参数,指向要申请的DDE项目。RequestData返回一个Pchar类型的无结束符字符串,包含了申请到的文本。返回字符串占用的内存必须在程序终止前显式释放。

        在人工模式下,即使存在一个DDEClientItem部件且与DDEClientConv相联接,数据更新后DDEClientItem部件的Text、Lines属性的值也不会改变。 

7.3.4 数据发送 

        数据发送与一般的DDE数据流向正好相反,是把数据从DDE客户应用程序发送到DDE服务器应用程序。

        数据发送使用DDEClientConv部件的两个方法PokeData 和 PokeDataLines, 它们的语法是: 

  function PokeData (Item: String ; Data: PChar): Boolean;

  function PokeDataLines (Item: String ; Data: TStrings): Boolean; 

        参数Item是DDE服务器中被联接的项目,Data是要发送的数据。如果数据是一个字符串,则把它转化为PChar类型并调用PokeData方法;如果数据是一个字符串链表对象,可调用PokeDataLines方法。

        方法的返回值标志数据传送是否成功。因为有一些DDE服务器应用程序并不接收发送的数据。

下面的语句把编辑框中的内容发送给服务器: 

  StrPCopy(TheText , Edit1.text);

DDEClientConv1.PokeData(DDEClientItem1.DDEItem , TheText); 

过程StrPCopy把一个Pascal类型的字符串拷贝到一个无结束符的PChar类型字符串中。 


想死你们了!

TOP