Visual FoxPro 不但仍然支持标准的过程化程序设计,而且在语言上还进行了扩展,提供了面向对象程序设计的强大功能和更大的灵活性。
面向对象的程序设计方法与编程技术不同于标准的过程化程序设计。程序设计人员在进行面向对象的程序设计时,不再是单纯地从代码的第一行一直编到最后一行,而是考虑如何创建对象,利用对象来简化程序设计,提供代码的可重用性。对象可以是应用程序的一个自包含组件,一方面具有私有的功能,供自己使用;另一方面又提供公用的功能,供其他用户使用。
本章内容要点:
在 Visual FoxPro 中,表单及控件是应用程序中的对象。用户通过对象的属性、事件和方法程序来处理对象。
Visual FoxPro 面向对象的语言扩展部分为应用程序中的对象提供了更多的控件,同时也使得创建和维护可重用代码库更为容易。它有如下优点:
面向对象程序设计基本上是一种包装代码,代码可以重用而且维护起来很容易。其中最主要的包装概念被称为类。
类和对象关系密切,但并不相同。类包含了有关对象的特征和行为信息,它是对象的蓝图和框架。例如,电话的电路结构和设计布局可以是一个类,而这个类的实例--对象,便是一部电话。
类决定了对象的特征
每个对象都有属性。例如,一部电话有一定的颜色和大小。当把一部电话放在办公室中,它又有了一定的位置,而它的听筒也有拿起和挂上两种状态。
在 Visual FoxPro 中,创建的对象也具有属性,这些属性由对象所基于的类决定。属性值既能在设计时刻也可在运行时刻进行设置。
例如,下表列出了一个复选框可能有的属性。
属性 | 说明 |
Caption | 复选框旁边的说明性文字。 |
Enabled | 复选框能否被用户选择。 |
ForeColor | 标题文本的颜色。 |
Left | 复选框左边的位置。 |
MousePointer | 在复选框内鼠标指针的形状。 |
Top | 复选框顶边的位置。 |
Visible | 指定复选框是否可见。 |
每个对象都可以对一个被称为事件的动作进行识别和响应。事件是一种预先定义好的特定动作,由用户或系统激活。在多种情况下,事件是通过用户的交互操作产生的。例如,对一部电话来说,当用户提起听筒时,便激发了一个事件,同样,当用户拨号打电话时也激发了若干事件。
在 Visual FoxPro 中,可以激发事件的用户动作包括:单击鼠标、移动鼠标和按键。
方法程序是与对象相关联的过程,但又不同于一般的 Visual FoxPro 过程。方法程序紧密地和对象连接在一起,并且与一般 Visual FoxPro 过程的调用方式也有所不同。
事件可以具有与之相关联的方法程序。例如,为 Click 事件编写的方法程序代码将在 Click 事件出现时被执行。方法程序也可以独立于事件而单独存在,此类方法程序必须在代码中被显式地调用。
事件集合虽然范围很广,但却是固定的。用户不能创建新的事件,然而方法程序集合却可以无限扩展。
下表列出了与复选框相关联的一些事件。
事件 | 说明 |
Click | 用户单击复选框。 |
GotFocus | 用户选择复选框。 |
LostFocus | 用户选择其他控件。 |
下表列出了与复选框相关联的一些方法程序。
方法程序 | 说明 |
Refresh | 复选框中的值被更新,以反映隐含数据源的数据变化。 |
SetFocus | 焦点被置于复选框,好象用户刚使用 TAB 键选中复选框。 |
有关事件出现次序的详细介绍,请参阅第四章“深入了解事件模型”。
所有对象的属性、事件和方法程序在定义类时被指定。此外,类还有如下特征,这些特征对提高代码的可重用性和易维护性很有用处。
当您在办公室内安装一部电话的时候,您也许并不关心这部电话在内部如何接收呼叫,怎样启动或终止与交换台的连接,以及如何将拨号转换为电子信号。您所要知道的全部信息就是您可以拿起听筒,拨打合适的电话号码,然后与您要找的人讲话。在这里,如何建立连接的复杂性被隐藏起来。所谓抽象性便是指能够忽略对象的内部细节,使用户集中精力来使用对象的特性。
内部的复杂性可以被隐藏起来。
封装使抽象性成为可能。封装就是指将对象的方法程序和属性代码包装在一起。例如您可以把确定列表框选项的属性和选择某选项时所执行的代码封装在一个控件里,然后把该控件加到表单中。
一个子类可以拥有其父类的全部功能,在此基础上,可添加其他控件或功能。例如,现有一个表示基本电话的类,用户可以定义其子类,该子类可拥有这个基本电话类的全部功能,用户还可添加上自己需要的其他功能。
子类可以重复使用代码
定义子类是减少代码的一条途径。先找到与自己所需最相似的对象,然后对它进行定制。
继承性的概念是使在一个类上所做的改动反映到它的所有子类当中。这种自动更新节省了用户的时间和精力。例如,电话制造商想以按键电话代替以前的拨号电话。若只改变主设计框架,并且基于此框架生产出的电话机能自动继承这种新特点,而不是逐部电话去改造,会节省大量的时间。
继承性减少了维护代码的难度。
继承性只体现在软件中,而不可能在硬件中实现。若发现类中有一个小错误,用户不必逐一修改子类的代码,只需要在类中改动一处,然后这个变动将体现在全部子类中。
如果您正在创建 Visual FoxPro 用户自定义类,下图可以帮助您理解 Visual FoxPro 类分层结构。
Visual FoxPro 类的分层结构
Visual FoxPro 的类有两大主要类型,因此 Visual FoxPro 对象也分为两大类型,它们便是容器类和控件类。
容器类和控件类
容器类可以包含其他对象,并且允许访问这些对象。例如,若创建一个含有两个列表框和两个命令按钮的容器类,而后将该类的一个对象加入表单中,那么无论在设计时刻还是在运行时刻,都可以对其中任何一个对象进行操作。不仅可以轻松地改变列表框的位置和命令按钮的标题,也可以在设计阶段给控件添加对象。例如,可以给列表框加标签,来标明该列表框。
下表列出了每种容器类所能包含的对象。
容器 | 能包含的对象 |
命令按钮组 | 命令按钮 |
容器 | 任意控件 |
控件 | 任意控件 |
自定义 | 任意控件、页框、容器和自定义对象 |
表单集 | 表单、工具栏 |
表单 | 页框、任意控件、容器或自定义对象 |
表格列 | 表头和除表单集、表单、工具栏、计时器和其他列以外的其余任一对象。 |
表格 | 表格列 |
选项按钮组 | 选项按钮 |
页框 | 页面 |
页面 | 任意控件、容器和自定义对象 |
项目 | 文件、服务程序 |
工具栏 | 任意控件、页框和容器 |
控件类的封装比容器类更为严密,但也因此丧失了一些灵活性。控件类没有 AddObject 方法程序。
在很多情况下都可以使用类。通过精心的计划,您可以有效地决定应该设计哪些类,以及在类中应该包含哪些功能。
可为使用过的每个控件和每个表单创建一个类,但显然,这不是设计应用程序最有效的方法。这样做的后果是很多类做同样的事情,却必须分别维护它们。
请为通用的功能创建控件类。例如,允许用户在表中移动记录指针的命令按钮、关闭表单的按钮以及帮助按钮等,都可以保存为类,并可以在需要表单具有这些功能时,把它们添加到表单中。
应该让用户了解您所设计的类的属性和方法程序,这样用户就可以在表单或表单集的特定数据环境中使用它们。
创建外观独特的表单集类、表单类和控件类,可以使应用程序的所有组件具有相同的风格。例如,可以在一个表单类中添加图象和特殊颜色的图案,并且把它作为所有被创建表单的模板;也可以创建具有独特外观(如带阴影效果)的文本框类,并在应用程序中所有需要文本框的地方使用这个类。
Visual FoxPro 允许创建多种不同的类,每一种类型的类都有自己的特点。可以在“新建类”对话框中指定想要创建的类的类型,也可在 CREATE CLASS 命令中使用 AS 命令子句指定类的类型。
可以在“类设计器”中创建 Visual FoxPro 大部分基类的子类:
Visual FoxPro 的基类
* 这些类是父容器的集成部分,在“类设计器”中不能子类化。
所有 Visual FoxPro 基类有如下的最小事件集。
事件 | 说明 |
Init | 当对象创建时激活。 |
Destroy | 当对象从内存中释放时激活。 |
Error | 当类中的事件或方法程序过程中发生错误时激活。 |
所有Visual FoxPro 基类有如下的最小属性集。
属性 | 说明 |
Class | 该类属于何种类型。 |
BaseClass | 该类由何种基类派生而来,例如 Form、Commandbutton 或 Custom 等 |
ClassLibrary | 该类从属于哪种类库。 |
ParentClass | 对象所基于的类。若该类直接由 Visual FoxPro 基类派生而来,则 ParentClass 属性值与 BaseClass 属性值相同。 |
在创建子类后,您可以设置自己的默认控件属性。例如,如果想让添加到应用程序表单中的控件的默认名字自动反映您的命名习惯,可以通过创建 FoxPro 基类的子类来做到这一点。还可以创建具有自定义外观和动作的表单类,把它作为所有要创建的表单的模板。
还可以在 Visual FoxPro 基类的基础上,创建具有封装功能的控件。例如,假设需要一个按钮,在单击该按钮时释放表单。可以在 Visual FoxPro 命令按钮类的基础上创建一个类,将它的标题属性设置为“退出”,并在 Click 事件中包括下面的命令:
THISFORM.Release
您可以将这个新按钮添加到应用程序的任何表单中。
添加到表单的定制命令按钮
子类可以不限于单个基类。事实上,可以在一个类定义中添加多个控件。Visual FoxPro 示例类库中的许多类都属于这种情况。例如,BUTTONS.VCX 中的 VCR 类就包含了四个用于在表中定位记录的命令按钮,Buttons.vcx 文件位于 Visual Studio …\的\Samples\Vfp98\Classes目录下。
在运行时,基于 Visual FoxPro 自定义类的子类没有可视元件。使用“类设计器”,可为自定义类创建方法程序和属性。例如,可以创建一个名为 StrMethods
的自定义类,它包含一些用于处理字符串的方法程序。可以将这个类添加到一个有编辑框的表单中,并在需要时调用这些方法程序。如果有一个名为 WordCount
的方法程序,就可以这样调用它:
THISFORM.txtCount.Value = ;
THISFORM.StrMethods.WordCount(THISFORM.edtText.Value)
非可视类(如自定义控件和计时器控件)只有在设计时刻在“表单设计器”中是可见的。当把自定义类添加到表单时,如果想在“表单设计器”中显示它,需将自定义类的图片属性设置为一个 .bmp 文件。
在“类设计器”中可以创建新类,并且在设计时就能看到每个对象的最终外观。
若要创建一个新类
– 或者 –
– 或者 –
在“新建类”对话框中,可以指定新类的名称、新类基于的类以及保存新类的类库。
创建新类
在创建类之后,还可以修改它,对类的修改将影响所有的子类和基于这个类的所有对象。也可以增加类的功能或修改类的错误,所有子类和基于这个类的所有对象都将继承修改。
若要在“项目管理器”中修改类
“类设计器”将打开。
也可以用 MODIFY CLASS 命令修改一个可视类定义。
重要内容 如果类已经被任何一个其他应用程序组件使用,就不应该修改类的 Name 属性,否则,Visual FoxPro 在需要时不能找到这个类。
可以用两种方法创建用户自定义类的子类。
若要创建用户自定义类的子类
– 或者 –
例如,要让一个新类 x
基于 Mylibrary.vcx 中的 parentclass
类,可用下面的代码:
CREATE CLASS x OF y AS parentclass ;
FROM mylibrary
当指定了新类的基类及存储该基类的库,“类设计器”打开。
类设计器
“类设计器”的用户界面与“表单设计器”相同,在“属性”窗口中可以查看和编辑类的属性,在“代码”编辑窗口中可以编写各种事件和方法程序的代码。
如果新类基于控件类或容器类,则可以向它添加控件。和向“表单设计器”中添加控件一样,在“表单控件”工具栏中选择所要添加的控件的按钮,将它拖动到“类设计器”中,再调整它的大小。
不论新类是基于什么类,都可以设置属性和编写方法程序的代码,也可以为该类创建新的属性和方法程序。
可以向新类中添加任意多的新属性和新方法程序。属性保存值,而方法程序则保存调用时可以运行的过程代码。
为类创建新属性和新方法程序的方式与将属性和方法程序添加到表单的方式相同。这些属性和方法程序属于类,而不属于类的单个组件。
若要将新属性添加到类
“公共”属性可在应用程序的任何位置被访问。“保护”和“隐藏”属性及方法程序将在本章稍后的“保护和隐藏类成员”中讨论。
"新建属性"对话框
您还可以添加有关属性的说明。当把控件添加到表单或表单集时,这个说明在“类设计器”和“表单设计器”中的“属性”窗口下端显示出来。
疑难解答 当给类加入了一个可由用户设置的属性时,用户可能给属性输入一无效的设置,而导致运行时刻错误的出现。因此您应该明确地说明这个属性的有效设置。例如,如果一个属性能设置为 0、1 或 2,应该在“新建属性”对话框的“说明”框中说明这些情况。您也可以在引用该属性的代码中检验属性值的有效性。
创建数组属性
例如,创建一个十行两列,名称为 myarray 的数组属性,在“属性名名称”框中键入以下内容:
myarray[10,2]
设计时刻数组为只读,并在“属性”窗口中以“斜体”字体显示。数组的属性可以在运行时刻被修改或被重新声明。数组属性的使用示例,请参阅第九章“创建表单”中的“管理表单的多个实例”。
若要在类中新增方法程序
在查询属性值或试图更改属性值时,Access 和 Assign 方法程序将执行代码。
在查询属性值时,执行 Access 方法程序中的代码,查询的方式一般是:使用对象引用中的属性、将属性值存储到变量中,或用问号 (?) 显示属性值。
当您试图更改属性值时,将执行 Assign 方法程序中的代码,更改属性值一般是使用 STORE 或 = 命令为属性赋新值。
有关 Access 和 Assign 方法程序的详细内容,请参阅 Access 和 Assign 方法程序。
也可以加入有关方法程序的说明。
类定义中属性和方法程序被默认为“公共”,其他类或过程中的代码可设置“公共”属性,调用公共方法程序。若属性和方法程序设置为“保护”则只能被该类定义内的方法程序或该类的子类所访问。若属性和方法程序设置为“隐藏”则只能被该类的定义内成员所访问。该类的子类不能“看到”或引用它们。
为确保类的功能正确,有时需要防止用户在编程时改变属性或在类的外面调用类外面的方法程序。
下面的例子描述了在类中使用被保护的属性和方法程序。
stopwatch 类包含在 Samples.vcx 文件中,该文件位于 Visual Studio …\Samples\Vfp98\Classes 目录中,该类包含了一个计时器和五个标签以显示经历的时间:
Samples.vcx 中的 stopwatch 类
Stopwatch 类包含标签和计时器。
ClassStopwatch 类的属性设置
控件 | 属性 | 设置 |
lblSeconds | 标题 | 00 |
lblColon1 | 标题 | : |
lblMinutes | 标题 | 00 |
lblColon2 | 标题 | : |
lblHours | 标题 | 00 |
tmrSWatch | 间隔 | 1000 |
此类还有三个被保护的属性,nSec、nMin 和 nHour,和一个被保护的方法程序 UpdateDisplay。类中其他三个自定义方法程序,Start、Stop 和 Reset 不被保护。
提示 选择“类”菜单中的“类信息”项,可查看类的所有属性和方法程序的可视性。
在 UpdateDisplay 方法程序和 Timer 事件的内部使用了被保护的属性。UpdateDisplay 方法程序修改标签的标题以反映经过的时间。
UpdateDisplay 方法程序
代码 | 注释 |
|
将数值属性的数据转换为字符型以在标签中显示。 |
|
设置标签标题,若要显示的数值小于 10(只有一位数)则显示时在首位加 0。 |
下表列出了 tmrSWatch.Timer
事件的代码:
Timer 事件
代码 | 注释 |
|
Timer 事件触发时(每秒一次),nSec 加 1 。若 nSec 达到 60,将它重设为 0 并使 nMin 加 1 。 |
|
若 nMin 达到 60 ,将它重设为 0 并使 nHour 加 1 。
完成设置新属性值后,调用 |
类有三个未被保护的方法程序:Start、Stop 和 Reset。用户可直接调用这些方法程序以控制 stopwatch 类。
Start 方法程序包含以下代码:
THIS.tmrSWatch.Enabled = .T.
Stop 方法程序包含以下代码:
THIS.tmrSWatch.Enabled = .F.
Reset 方法程序设置被保护的属性为 0,并调用被保护的 UpdateDisplay 方法程序。
THIS.nSec = 0
THIS.nMin = 0
THIS.nHour = 0
THIS.UpdateDisplay
用户不能直接设置这些属性或调用这个方法程序,但 Reset 方法程序可以。
创建一个新属性时,默认设置为“假”(.F.)。要为属性指定一个不同的默认值,可使用“属性”窗口:在“其他”选项卡中,单击这个属性并将它设置为需要的值。在把类添加到表单或表单集时,该值将作为初始的属性设置。
也可以在“类设计器”中设置任何一个基类属性。当把一个基于这个类的对象添加到表单时,对象将反映修改后的属性设置,而不是 Visual FoxPro 原有的属性设置。
提示 如果想使属性的默认设置为空字符串,可选择“属性编辑属性设置框”框中的设置,然后按 BACKSPACE 键。
可以在“类信息”对话框中,为类指定工具栏图标和容器图标。
若要为类设置一个工具栏图标
提示 工具栏图标的 .BMP 文件必须是 15 乘 16 像素点大小。如果图片过大或过小,它将被调整到 15 乘 16 像素点,图形可能变形。
把类和工具栏一起放入类库后,工具栏图标将显示在“表单控件”工具栏中。
通过设置容器图标可指定“项目管理器”和“类浏览器”中类的显示图标。
若要为类设置一个容器图标
每个经过可视设计的类都可保存在以 .vcx 为扩展名的类库中。
可以用三种方法创建类库。
若要创建一个类库
– 或者 –
例如,下面的语句创建了一个名为 myclass
的新类和一个名为 new_lib
的新类库:
CREATE CLASS myclass OF new_lib AS CUSTOM
– 或者 –
例如,在“命令”窗口键入下面的命令,可以创建一个名为 new_lib
的类库:
CREATE CLASSLIB new_lib
把类库添入项目后,您可以很容易地将类从一个类库复制到另一个类库,或将类从类库中删除。
若要将类从一个类库复制到另一个类库
提示 为了快速简便,可以将类和基于这个类的所有子类都包含在一个类库中。如果一个类包含多个不同类库中的元件,那么在运行时刻或设计时刻,最初加载这个类时将花费较长的时间,因为包含类元件的类库必须全部打开。
若要从库中删除一个类
– 或者 –
用 RENAME CLASS 命令可改变类的名称。但是必须注意,改变了一个类的名称后,在其他 .vcx 文件中,包含这个类和子类的表单仍然引用旧名称,这样就不能正确地工作。。
FoxPro 包含一个“类浏览器”,使得使用和管理类和类库更加方便。有关详细内容,请参阅“类浏览器”窗口。
您可以将类从“项目管理器”拖至“表单设计器”或“类设计器”中,也可以将类注册,这样便可以在“表单设计器”或“类设计器”的“表单控件”工具栏上直接显示它们,还能用和添加标准控件同样的方法将它们添加到容器中。
若要注册一个类库
也可以使用“查看类”按钮,选择子菜单中的“添加”,将自定义类库添加到“控件”工具栏。为使这些类在以后的 Visual FoxPro 工作期中仍然在“控件”工具栏中有效,需要在“选项”对话框中设置为默认值。
将基于用户自定义类的对象添加到表单时,可以修改类中所有未保护的属性,覆盖默认的设置。如果以后在“类设计器”中再修改该类的属性,将不会影响表单中对象的设置。如果没有修改表单中对象的属性设置而修改了类中的属性设置,则对象的相关属性同样也会改变。
例如,用户将一个基于类的对象添加到表单,并且将 BackColor 属性从白色改成红色。如果再将类的 BackColor 属性改成绿色,用户表单上的对象的 BackColor 属性仍然是红色。另一方面,如果用户没有修改对象的 BackColor 属性,而您将类的 BackColor 属性改为绿色,那么表单上的对象将继承这一修改,也变为绿色。
对象和子类自动继承基类的功能。但同时您也可以用新的功能替代这些继承来的功能。比如,您由某个基类派生出子类,或把基于这个类的对象加入到一个容器中时,重新为相应的 Click 事件编程,那么,在运行时刻,基类的代码不会运行,而运行新的代码。
而更多的时候,您希望向新类或对象中添加新功能的同时,保留父类功能。事实上,在面向对象的程序设计中很关键的一点就是决定哪些功能应包含在基类中,哪些功能应包含在子类中,哪些功能应包含在对象中。您可以在类或容器层次的各级代码中使用函数 DODEFAULT( ) 或作用域操作符 (::) 来优化类的设计。
使用函数 DODEFAULT( ) 可从子类中调用父类代码。
例如,cmdOK
是一命令按钮,存储在
Visual Studio …\Samples\Vfp98\Classes 目录下的 Buttons.vcx 类库中,该按钮 Click 事件中的代码的作用是释放按钮所在的表单。CmdCancel
是 cmdOK
的子类,它也保存在同一类库中。cmdCancel
中增加了放弃修改的功能。例如,可在
Click 事件中加入以下代码:
IF USED( ) AND CURSORGETPROP("Buffering") != 1
TABLEREVERT(.T.)
ENDIF
DODEFAULT( )
由于表关闭时,对表的修改自动地记录在缓冲表中,所以不必将 TABLEUPDATE( ) 代码加入 cmdOk
中。cmdCancel
中新加的代码先放弃修改,将修改前的数据恢复到表中,然后调用父类 cmdOk
中的代码,释放表单。
类的层次结构和容器的层次结构是 Visual FoxPro 中两个独立的范畴。Visual FoxPro 在类层次结构中逐层向上地查找事件代码,而对象在容器层次结构中被引用。下面的“在容器层次中引用对象”一节将讨论容器的层次结构。本章稍后的“按类层次调用事件代码”部分将对类的层次结构作出解释。
若要处理一个对象,需要知道它相对于容器分层结构的关系。例如,如果要在表单集中处理一个表单的控件,则需要引用表单集、表单和控件。
在容器层次中引用对象恰似给 Visual FoxPro 提供这个对象地址。例如,当您给一个外乡人讲述一个房子的位置时,您需要根据其距离远近,指明这幢房子所在的国家(地区)、省份(州)、城市、街道,甚至这幢房子的门牌号码,否则将引起混淆。
下图表示了一种可能的容器嵌套方式。
嵌套容器
若要使表格中某列的控件无效,需要提供以下地址:
Formset.Form.PageFrame.Page.;
Grid.Column.Control.Enabled = .F.
应用程序对象(_VFP)的 ActiveForm 属性允许在不知道表单名的情况下处理活动的表单。例如,下列代码改变活动表单的背景颜色,而不考虑其所属的表单集。
_VFP.ActiveForm.BackColor = RGB(255,255,255)
类似地,ActiveControl 属性允许处理活动表单的活动控件。例如,当用户交互地选择各种控件时,如果在“监视”窗口左窗格内输入下列表达式,将在调试窗口的右窗格内显示表单中的活动控件名称。
_VFP.ActiveForm.ActiveControl.Name
在容器层次中引用对象时(例如表单集中,在表单上命令按钮的 Click 事件里),可以通过快捷方式指明所要处理的对象。下表列出了一些属性和关键字,这些属性和关键字允许更方便地从对象层次中引用对象:
属性或关键字 | 引用 |
Parent | 该对象的直接容器。 |
THIS | 该对象。 |
THISFORM | 包含该对象的表单。 |
THISFORMSET | 包含该对象的表单集。 |
注释 您只能在方法程序或事件代码中使用 THIS、THISFORM 和 THISFORMSET。
下表提供了使用 THISFORMSET、THISFORM、THIS 和 Parent 来设置对象属性的示例:
命令 | 包含命令的地方 |
|
在此表单集的任意表单的任意控件其事件或方法程序代码中。 |
|
在 cmd1 所在的同一表单的任意控件其事件或方法程序代码中。 |
|
在需要改变其标题的控件的事件或方法程序代码中。 |
|
在表单的一个控件的事件或方法程序代码中,此例的命令设置表单的背景色为暗红色。 |
若要设置属性
Container.Object.Property = Value
例如,下列语句设置 frmPhoneLog
表单中 txtDate
文本框的各种属性:
frmPhoneLog.txtDate.Value = DATE( ) &&
显示当前日期frmPhoneLog.txtDate.Enabled = .T. &&
控件有效frmPhoneLog.txtDate.ForeColor = RGB(0,0,0) &&
黑色文本frmPhoneLog.txtDate.BackColor = RGB(192,192,192) &&
灰色背景
在上例的属性设置中,frmPhoneLog 是最高层的容器对象。如果 frmPhoneLog 包含在一个表单集中,则需要在父路径上包含这个表单集:
frsContacts.frmPhoneLog.txtDate.Value = DATE( )
WITH ... ENDWITH 结构简化了设置多个属性的过程。例如,在表单集的表单中,要设置表格列的多个属性,可以使用以下的语法结构:
WITH THISFORMSET.frmForm1.grdGrid1.grcColumn1
.Width = 5
.Resizable = .F.
.ForeColor = RGB(0,0,0)
.BackColor = RGB(255,255,255)
.SelectOnEntry = .T.
ENDWITH
如果对象已创建,便可以在应用程序的任何一个地方调用这个对象的方法程序。
若要调用方法程序
Parent.Object.Method
frsFormSet.frmForm1.Show
frsFormSet.frmForm1.txtGetText1.SetFocus
在表达式中,有返回值的方法程序必须以圆括号结尾。例如,下列语句将用户自定义的 GetNewCaption
方法程序的返回值设置成表单的标题:
Form1.Caption = Form1.GetNewCaption( )
注释 传递给方法程序的参数必须放在方法程序名后面的圆括号中。例如,Form1.Show(nStyle)。将 nStyle 传递给 Form1 的 Show 方法程序代码。
当事件发生时,该事件的过程代码将被执行。例如,当用户单击命令按钮时,命令按钮的 Click 事件过程代码将被执行。
用编程方式,可以使用 MOUSE 命令产生 Click、DblClick、MouseMove 和 DragDrop 事件,或者使用 ERROR 命令产生 Error 事件,或者使用 KEYBOARD 命令产生 KeyPress 事件。除此以外,用户不能用其他的程序设计方法产生其他事件,但可以调用与这些事件相关的过程。例如,下面的语句使得 frmPhoneLog
的 Activate 事件代码被执行,但并不激活这个表单:
frmPhoneLog.Activate
若要激活表单,请使用表单的 Show 方法程序。调用 Show 方法程序将显示并激活表单,同时 Activate 事件代码也将被执行:
frmPhoneLog.Show
用户既可以在“类设计器”或“表单设计器”中可视地定义类,也可以在 .PRG 文件中以编程方式定义类。本节介绍如何编写类定义。有关特定的命令、函数和操作符的详细内容,请参阅“帮助”。有关表单的详细内容,请参阅第九章“创建表单”。
在程序文件中,正如程序代码不能在程序中的过程之后一样,程序代码只能出现在类定义之前,而不能在类定义之后。以下是创建类的语法的基本框架:
DEFINE CLASS ClassName1 AS ParentClass [OLEPUBLIC]
[[PROTECTED | HIDDEN PropertyName1, PropertyName2 ...]
[Object.]PropertyName = eExpression ...]
[ADD OBJECT [PROTECTED] ObjectName AS ClassName2 [NOINIT]
[WITH cPropertylist]]...
[[PROTECTED | HIDDEN] FUNCTION | PROCEDURE Name[_ACCESS | _ASSIGN]
[NODEFAULT]
cStatements
[ENDFUNC | ENDPROC]]...
ENDDEFINE
使用 DEFINE CLASS 命令中的 PROTECTED 和 HIDDEN 关键字可以保护和隐藏类定义中的属性和方法程序。
例如,创建一个类来保存雇员信息,又不希望雇员自己变动聘用日期,便可以将 HireDate 属性保护起来。若用户需要知道一个雇员何时开始被聘,可以使用一个方法程序来返回 HireDate。
DEFINE CLASS employee AS CUSTOM
PROTECTED HireDate
First_Name = ""
Last_Name = ""
Address = ""
HireDate = { - - }
PROCEDURE GetHireDate
RETURN This.HireDate
ENDPROC
ENDDEFINE
当已保存了一个可视类,则可在此基础上用 CREATEOBJECT( ) 函数创建该类的对象。在下面的例子中,我们将运行一个表单,这个表单作为类定义在类库 Forms.vcx 中。
创建和显示一表单对象,其类在“表单设计器”中设计
代码 | 注释 |
|
指明表单定义保存在哪个类库(.vcx 文件)中。关键字 ADDITIVE 规定在打开指定类库的同时并不关闭其他早先已打开的类库。 |
|
在此代码中假设保存在类库中的表单类名称为 TestForm。 |
|
显示表单。 |
可以使用 DEFINE CLASS 命令中的 ADD OBJECT 子句或 AddObject 方法程序向容器中添加对象。
例如,下面的类定义基于某个表单,ADD OBJECT 命令向该表单中加入两个命令按钮:
DEFINE CLASS myform AS FORM
ADD OBJECT cmdOK AS COMMANDBUTTON
ADD OBJECT PROTECTED cmdCancel AS COMMANDBUTTON
ENDDEFINE
当创建了容器对象之后,则需要使用 AddObject 方法程序向容器中加入对象。例如,下面代码创建了一个表单对象,然后向其加入两个命令按钮:
frmMessage = CREATEOBJECT("FORM")
frmMessage.AddObject("txt1", "TEXTBOX")
frmMessage.AddObject("txt2", "TEXTBOX")
在类的方法程序代码中,也可使用 AddObject 方法程序。例如,下面的类定义在与 Init 事件相关联的代码中使用 AddObject 方法程序向表格列中加入控件。
DEFINE CLASS mygrid AS GRID
ColumnCount = 3
PROCEDURE Init
THIS.Column2.AddObject("cboClient", "COMBOBOX")
THIS.Column2.CurrentControl = "cboClient"
ENDPROC
ENDDEFINE
使用 AddObject 方法程序,可以采用编程方式将对象添加到控件类或容器类中。也可以在类的 Load、Init 和其他方法程序中使用 CREATEOBJECT( ) 函数创建对象。
当用 AddObject 方法程序添加对象时,该对象就成为容器的一个成员,它的 Parent 属性指向这个容器。当基于容器类或控件类的对象从内存释放时,其上添加的对象也被释放。
使用 CREATEOBJECT( ) 函数创建对象时,该对象是基于某个定义的类,它的使用仅限于调用该函数的方法程序中的变量,此时没有定义对象的父属性。
除了为对象的方法程序和事件编写代码,还可以在 Visual FoxPro 基类的子类中扩展方法程序集。下面是编写事件代码和方法程序代码的规则:
当用户创建一个类时,这个类自动继承其父类的所有属性、方法程序和事件。如果代码是为父类事件而编写,则当子类对象该事件发生时执行此代码。当然,也可以在子类中改写此事件的父类代码。
如果父类和子类都有各自的响应某事件的代码,若要在子类中明确调用父类事件代码,使用 DODEFAULT( ) 函数。
例如,由命令按钮基类所派生的名为 cmdBottom 的子类有如下 Click 事件代码:
GO BOTTOM
THISFORM.Refresh
在向表单中加入一个名为 cmdBottom1
的该类的对象时,您也许想显示一条消息,告知用户记录指针已在表的最后一个记录上。这可以通过向对象的 Click 事件加入如下代码来实现:
WAIT WINDOW "At the Bottom of the Table" TIMEOUT 1
但当运行这个表单时,您却发现只显示消息而记录指针不动,这是因为并未执行父类的 Click 事件代码。要想保证父类的 Click 事件代码被执行,请在对象的 Click 事件过程中加入下行代码:
DODEFAULT( )
WAIT WINDOW "At the Bottom of the Table" TIMEOUT 1
注释 可以使用 ACLASS( ) 函数确定一个对象的类层次中的所有类。
有时在事件或方法程序中,也许需要防止基类的默认动作发生,这可以通过在方法程序代码中加入 NODEFAULT 关键字来实现。例如,下面的程序在文本框的 KeyPress 事件中,使用 NODEFAULT 关键字来防止在该文本框中显示键入的字符。
frmKeyExample = CREATEOBJECT("test") frmKeyExample.Show READ EVENTS DEFINE CLASS test AS FORM ADD OBJECT text1 AS TEXTBOX PROCEDURE text1.KeyPress PARAMETERS nKeyCode, nShiftAltCtrl NODEFAULT IF BETWEEN(nKeyCode, 65, 122) &&
在'A'
和'z'
之间This.Value = ALLTRIM(This.Value) + "*"
ACTIVATE SCREEN &&
将输出发送到Visual FoxPro
主窗口中?? CHR(nKeyCode)
ENDIF
ENDPROC
PROCEDURE Destroy
CLEAR EVENTS
ENDPROC
ENDDEFINE
许多应用程序的共同特点便是:使用系列定位按钮来允许用户在表中移动记录指针,其中典型的便是在表中上移或下移一个记录,或者移向表的首记录或尾记录。
表定位按钮
因为所有应用程序中用到的定位按钮序列都有一些共同的特点和动作,所以创建一个定位按钮类是一个好方法。在此之后,便可很容易地从这些共同的外观和功能中派生出您所需的按钮序列。其父类便是本节稍后将要介绍的Navbutton
类。
定义好父类之后,下面的子类便定义了四种定位按钮的特定功能和外观。这四类按钮为:navTop
、navPrio
r、navNext
、navBottom
。
最后,创建 vcr 容器类,每一种按钮都被加入到这个容器类中。该容器可以加入到表单或工具栏中,以提供表定位功能。
若要创建 Navbutton
,请将如下六个类定义(Navbutton
、navTop
、navBottom
、navPrior
、navNext
和 vcr
)保存至一个程序文件(如 NAVCLASS.PRG)中。
通用定位命令按钮类的定义
代码 | 注释 |
|
定义定位按钮的父类。 给类设置尺寸大小属性 。 使用 |
|
若设置了 TableAlias ,父类过程在子类定位代码执行以前选择有该别名的表,否则将假定用户需要对当前工作区中的表进行定位。 |
|
请使用 _SCREEN.ActiveForm.Refresh 来代替 THISFORM.Refresh。这使得该类即可加入表单也可加入工具栏中,并具有同样的功能。 |
|
类定义结束。 |
所有特定的定位按钮都基于 Navbutton 类。下面的代码定义了定位按钮集合中的 Top 按钮,其他的三类定位按钮将在后面的表中定义。因为这四个类定义很相似,所以只给出第一个类的详细注释。
Top 定位按钮类的定义
代码 | 注释 |
|
定义 Top 定位按钮类,并设置 Caption 属性。 |
|
创建控件发生 Click 事件所执行的方法程序代码。 |
|
调用父类 Navbutton 的 Click 事件代码,以便当 TableAlias 属性设置之后,可以选择适当的别名。包括将记录指针移向首记录的代码:GO TOP。 调用父类的 RefreshForm 方法程序,在这里没有必要使用作用域操作符 (::),因为在子类中没有与父类中此方法程序重名的方法程序。但在父类和子类中都有 Click 事件的方法程序代码。 |
|
Click 过程结束。 |
|
类定义结束 |
其他定位按钮具有相似的类定义。
其他定位按钮类的定义
代码 | 注释 |
|
定义 Next 定位按钮类,并设置 Caption 属性。 |
|
加入将记录指针移向表中下条记录的代码。 类定义结束。 |
|
定义 Prior 定位按钮类,并设置 Caption 属性。 |
|
加入将记录指针移向表中上条记录的代码。 类定义结束。 |
|
定义 Bottom 定位按钮类,并设置 Caption 属性。 |
|
加入把记录指针移向表中尾记录的代码。 类定义结束。 |
下面的类定义包含了所有四个定位按钮,使它们可以作为一个整体加入到表单中。该类也包含了设置按钮 TableAlias 属性的方法程序。
表定位控件类的定义
代码 | 注释 |
|
类定义开始。Height 属性被设为它所包含的命令按钮的高度。 |
|
加入定位按钮。 |
|
此方法程序用于设置按钮的 TableAlias 属性,TableAlias 属性在父类 Navbutton 中定义。也可以使用 SetAll 方法程序设置该属性: IF TYPE ("cTableAlias") = 'C' This.SetAll("TableAlias", "cTableAlias") ENDIF 但如果将对象加入到一个不含 TableAlias 属性的类中,将发生错误。 |
|
类定义结束 |
在定义类之后,就可以设计其子类或将此类加入到表单中。
用户也可以在 vcr
的基础上创建子类,这些子类可以拥有象“搜索”、“编辑”、“保存”、“退出”这样的附加按钮。例如,vcr2
包含一个“退出”按钮:
含有“退出”表单按钮的一组表定位按钮
表定位控件子类的定义
代码 | 注释 |
|
在 vcr 的基础上定义一个新类,并向其中加入一个命令按钮。 |
|
当用户单击 cmdQuit 时,将释放表单 。 |
|
类定义结束。 |
Vcr2
可以完成 vcr
类能做的任何事,此外还拥有一个新的命令按钮,而用户并不需要为此重写代码。
因为继承性,所有对父类的修改都将反映到其全部子类中。例如,可以通过在 navNext.Click
中修改 IF EOF(
)
语句,通知用户已到达表的尾记录。如下所示。
IF EOF( )
GO BOTTOM
SET MESSAGE TO "Bottom of the table"
ELSE
SET MESSAGE TO
ENDIF
通过修改 navPrior.Click
中的 IF BOF(
)
语句,可以通知用户已到达表的头记录。
IF BOF()
GO TOP
SET MESSAGE TO "Top of the table"
ELSE
SET MESSAGE TO
ENDIF
如果在 navNext
和 navPrior
类中做如上修改,那么 vcr
和 vcr2
中的相应按钮将自动作出改变。
将 vcr
定义为一个控件之后,就可以将其加入到一个容器的定义中。例如,下列加到 Navclass.prg 中的代码定义了含有附加定位按钮的表单。
DEFINE CLASS NavForm AS Form
ADD OBJECT oVCR AS vcr
ENDDEFINE
表单子类被定义之后,便可以使用适当的命令方便地显示它。
若要显示表单
SET PROCEDURE TO navclass ADDITIVE
NavForm
类的对象:
frmTest = CREATEOBJECT("navform")
frmTest.Show
当用户单击定位按钮时,如果不调用 oVCR
(在 NavForm
中的 VCR 对象)的 SetTable 方法程序,记录指针将在当前工作区的表中移动。可以通过调用 SetTable 方法程序指定记录指针在哪个表中移动。
frmTest.oVCR.SetTable("customer")
注释 用户关闭表单后,frmTest
被置为 null 值 (.NULL.)。若要释放内存中的对象变量,请使用 RELEASE 命令。在程序运行完成后,在程序文件中创建的对象变量将被释放。
表格包含列,而列又可以包含标头和其他控件。列中包含的默认控件为一个文本框,所以表格的默认功能很象一个“浏览”窗口。然而表格内部结构使它具有无限可扩展性。
下面的实例创建了一个含有表格对象的表单。该表格有两列,第二列包含了复选框,可以显示表中的逻辑字段值。
在列中含有复选框的表格控件
在表格列中含有复选框的表格类的定义
代码 | 注释 |
|
类定义开始。设置有关决定表格外观的属性。 设置 ColumnCount 属性值为 2,表示在表格中加入两列,每个列都含有名为 Header1 的标头。另外,每列都有自己一组独立属性,这些属性决定其外观和动作。 |
|
设置了一个列的 ControlSource 属性后,这列将显示表中所有记录中该字段的值。Discontinu 是一个逻辑型字段。 |
|
Column2 含有复选框,设置列的 Sparse 属性为 .F.,使得所有行上复选框皆可见,而不仅仅在选中单元内可见。 |
|
设置列宽和标头标题。 AddObject 方法程序允许向容器内加入对象,在这里是一个名为 chkl 的复选框。设置列的 CurrentControl 属性,显示这个复选框。 确认该复选框可见。 设置标题为空字符串,这样其默认标题“chk1”就不显示。 |
|
类定义结束。 |
下面的类定义是含有表格的表单,两个类定义可以包含在同一个程序文件中。
含有一个表格类的表单类定义
代码 | 注释 |
|
创建一个表单类,并添加一个基于表格类的对象。 |
|
创建该类对象的程序将使用 READ EVENTS。在表单的 Destroy 事件中加入 CLEAR EVENTS,使用户关闭表单时终止程序运行。 类定义结束。 |
下面的程序打开一个表,表中的字段显示在表格列中。同时创建基于 GridForm 类的对象,然后发出 READ EVENTS 命令。
CLOSE DATABASE
OPEN DATABASE (HOME(2) + "data\testdata.dbc")
USE products
frmTest= CREATEOBJECT("GridForm")
frmTest.Show
READ EVENTS
这个程序可以与类定义放于同一文件中,但它须放在文件的开始部分。也可以使用 SET PROCEDURE TO 命令来指明含有类定义的程序,而将此代码包含在一个单独的程序中。
创建对象的引用不等于复制对象。引用比添加对象占用更少的内存,可以很容易在过程之间传递,并且有助于编写通用代码。
有时需要通过一个或更多的引用来处理对象。例如,下列程序定义了一个类,创建一个基于此类的对象,然后返回对这个对象的引用。
*--NEWINV.PRG *--
返回对新的发票表单的引用frmInv = CREATEOBJECT("InvoiceForm")
RETURN frmInv
DEFINE CLASS InvoiceForm AS FORM
ADD OBJECT txtCompany AS TEXTBOX
*
设置属性、添加其他对象等所需代码ENDDEFINE
对于在 NEWINV.PRG 中创建的对象,下面程序建立对它的引用。完全可以象处理一个对象变量那样处理引用变量。
frmInvoice = NewInv() &&
将对象的引用存储在变量中frmInvoice.SHOW
如下例所示,您可以创建对表单上对象的引用。
txtCustName = frmInvoice.txtCompany
txtCustName.Value = "Fox User"
提示 创建了对象之后,您可以用 DISPLAY OBJECTS 命令显示该对象的类的层次结构,属性设置,所包含的对象,和可用的方法程序和事件。可使用 AMEMBERS( ) 函数将属性(不是属性的设置)、事件、方法程序和所包含的对象填入数组。
若一个对象的引用存在,则释放该对象并不能从内存中完全清除它。例如,下面的命令释放原始对象 frmInvoice:
RELEASE frmInvoice
然而,因为存在对 frmInvoice
中所包含的对象 txtCompany 的引用,这个对象并不能从内存中释放,要等到使用如下命令释放了 txtCustName
以后才可全部释放:
RELEASE txtCustName
可以使用 TYPE( )、ISNULL( ) 和 VARTYPE( ) 函数检查对象是否存在。例如,下行代码检查名为 oConnection
的对象是否存在:
IF TYPE("oConnection") = "O" AND NOT ISNULL(oConnection) *
对象存在ELSE
*
对象不存在ENDIF
注释 因为当关闭表单后,.NULL. 被存入这个表单的对象变量中,所以必须使用 ISNULL( ),但这个变量的类型仍为“O”。
可以把类成员定义成数组。在下面的示例中,Choices
是关于一组控件的数组。
DEFINE CLASS MoverListBox AS CONTAINER DIMENSION choices[3] ADD OBJECT lstFromListBox AS LISTBOX ADD OBJECT lstToListBox AS LISTBOX ADD OBJECT choices[1] AS COMMANDBUTTON ADD OBJECT choices[2] AS COMMANDBUTTON ADD OBJECT choices[3] AS CHECKBOX PROCEDURE choices.CLICK PARAMETER nIndex DO CASE CASE nIndex = 1 *
代码CASE nIndex = 2
*
代码CASE nIndex = 3
*
代码ENDCASE
ENDPROC
ENDDEFINE
当用户单击控件数组中的一个控件时,Visual FoxPro 将该控件的索引序号传递给 Click 事件过程。在过程中可以使用 CASE 语句,根据不同的选定按钮执行不同的代码。
也可以创建对象的数组。例如,MyArray 保存五个命令按钮:
DIMENSION MyArray[5]
FOR x = 1 TO 5
MyArray[x] = CREATEOBJECT("COMMANDBUTTON")
ENDFOR
在使用对象数组时,请注意以下问题:
MyArray.Enabled = .F.
面向对象的语言中,类提供了方便实用的信息载体,用于存贮与实体相联系的数据和过程。例如,可以定义一个顾客类,保存顾客的信息以及计算顾客年龄的方法程序。
DEFINE CLASS customer AS CUSTOM
LastName = ""
FirstName = ""
Birthday = { - - }
PROCEDURE Age
IF !EMPTY(THIS.Birthday)
RETURN YEAR(DATE()) - YEAR(THIS.Birthday)
ELSE
RETURN 0
ENDIF
ENDPROC
ENDDEFINE
然而,保存在顾客类对象中的数据只保留在内存中。如果数据保存在表中,这个表将存储在硬盘上。在搜索多个顾客时,表提供了访问全部 Visual FoxPro 数据库管理命令和函数的能力,因此可以快速定位信息,将其排序或将其分组,在其上进行计算,创建报表和查询等。
在表上和数据库中保存数据和处理数据是 Visual FoxPro 最擅长的。当然,有时也需要将数据保存在对象中,但通常仅在应用程序运行时,并且数据与一个实体相关时才这样做。
例如,在一个含有安全系统的应用程序中,通常用一个表保存允许访问本程序的用户,这个表包含用户标识、密码和访问权限。用户登录之后,便不再需要这张表的所有信息,而只需要当前用户信息,在对象中则很容易实现这些信息的保存和处理。下面的类定义中,举例说明当基于此类的对象被创建后的登录过程。
DEFINE CLASS NewUser AS CUSTOM PROTECTED LogonTime, AccessLevel UserId = "" PassWord = "" LogonTime = { - - : : } AccessLevel = 0 PROCEDURE Init DO FORM LOGON WITH ; &&
假设已经建立了这个表单This.UserId, ;
This.PassWord, ;
This.AccessLevel
This.LogonTime = DATETIME( )
ENDPROC
*
建立方法程序来返回保护系统的属性值.
PROCEDURE GetLogonTime
RETURN This.LogonTime
ENDPROC
PROCEDURE GetAccessLevel
RETURN This.AccessLevel
ENDPROC
ENDDEFINE
在主程序中,可以创建基于 NewUser
类的对象:
oUser = CREATEOBJECT('NewUser')
oUser.Logon
在应用程序的任何地方,当需要当前用户信息时,都可以从 oUser 对象中获得。例如:
IF oUser.GetAccessLevel( ) >= 4
DO ADMIN.MPR
ENDIF
在多数程序中,都可以通过对象和数据的集成来充分发挥 Visual FoxPro 的强大功能。大多数 Visual FoxPro 类都有属性和方法程序,使您可以将关系型数据库管理系统和面向对象系统合在一起使用。
用于集成 Visual FoxPro 类和数据库数据的属性
类 | 数据属性 |
表格 | RecordSource、ChildOrder、LinkMaster |
所有其他控件 | ControlSource |
列表框和组合框 | ControlSource、RowSource |
表单和表单集 | DataSession |