Foxpro 创建你自己的向导
Rod Paddock 和John Petersen 也在他们的书中也提供了一些使用VFP 向导类来建立你自己的向导的细节。这样做的优点是,有人已经建立了所有的"引擎"来管理向导处理,且你的向导将与其它的VFP向导具有相同的感观。
用BuilderD 创建生成器
在查看VFP6中的FoxPro基本类时(FFC),我注意到一件事是它们都具有叫做Builder和BuilderX的自定义属性,并且各个类的BuilderX 是设置为= HOME() + "Wizards\BuilderD,BuilderDForm"。我知道这些属性的作用(他们告诉VFP,这个类的生成器名),但为什么每一个类都要指定相同的生成器呢?更有趣的是,各个类以不同的选项调用类似的生成器表单(图1显示_HyperLinkLabel 类的生成器)。当使用相同的类时将会发生什么情况呢?
首先介绍一点背境。可能正象你可能意识到的,VFP 生成器可能以多种方式调用,但最常见的是可能是在对象上右击鼠标并从出现的菜单上选择生成器。这会使BUILDER.APP 被执行。BUILDER.APP 检查是否选定的对象(我们称为"目标对象") 具有 BuilderX 属性,如果有,运行该属性指定的程序或实例化该该属性指定的类(如果生成器是一个类,将指定一个类库,逗号及类名)。如果它没有 BuilderX 属性但有一个 Builder 属性,生成器运行该属性指定的程序或实例化该该属性指定的类(我们将看到为什么会同时用两个属性指定生成器)。如果两个属性都不存在,将使用该对象基类的默认生成器。 你可以在你的类中建立自定义的 Builder 和 BuilderX 属性(甚至在你的基类中) 然后为每一个类填写适当的生成器名字。要使用两个属性的理由是 BuilderX 为该类指定一个自定义生成器 而Builder为一组公共的类(如comboboxe 或 grid)指定一个所需的生成器。正如我们稍后会看到的一样,我们可以在 BuilderX 属性中指定的生成器中,单击一个按钮来调出在 Builder 属性中指定的生成器。 因此,我们可以很容易地在 Builder 和 BuilderX 属性中指定生成器。而不是花很多时间来建立你自己的生成器,特别对于不常使用的类。
BuilderD
你可能意识到了 BuilderB 技术 用来建立生成器是容易的和快速的。BuilderB 是一组你可以派生来建立你自己的生成器的类。你可以为你想用生成器维护的目标对象的各属性添加一个控件到生成器子类。虽然BuilderB 使得建立生成器更容易,但你必须为每一个你想用生成器管理的类建立一个新的生成器子类。对于我们这样的懒人来说,幸运的是,Ken 以数据驱动的方式增强了BuilderB。这种新技术叫做BuilderD ,"D" 的意思是"动态" (Dynamic),可以在Ken 的网站(www.classx.com)找到,也包含在VFP 6 中(在VFP 主目录下的WIZARDS 目录中的BUILDERD.VCX)。 BuilderD 由一系列的类组成,但主要的一个是BuilderDForm; 这是一个数据驱动生成器表单 (注意这是VFP6.0的基本类中指定的BuilderX类)。正如你在图2中看到,该表单中的文本框用于输入目标类的名字和类名,按钮提供的功能可以调出类浏览器和显示帮助,一个页框、两个页面,但没有用于管理属性值的控件。
以下是该表单在实例化时,如何组合适当的控件:
-
Init 方法调用SetObject 方法,它调用AddObjects 方法(该代码事实上是在BuilderDForm 的父类BuilderBaseForm中)。
-
AddObjects 方法调用来自BuilderDB类的表单上的oBuilderDB 对象的AddObjects 方法。
-
BuilderDB.AddObjects 是一个复杂的方法,但其基本操作是打开生成器定义表(默认情况下是在VFP 主目录下的WIZARDS 目录中的BUILDERD.DBF,但可以修改cBuilderTable 属性来指定另一个表),查找目标对象类的记录,查找所有相似的记录,并使用该记录中的信息在页框中的一个或多个页上建立控件。这些控件是基于BUILDERD.VCX中的类的,诸如BuilderCheckBox 和 BuilderTextBox,它们知道如何绑定属性到目标对象。
这些步骤的结果是,一个生成器可以管理目标对象的一个或更多的属性。一个指定的生成器可以事实上做得比这些还要多,例如放置代码到目标对象的方法或对象的容器中,介是目前我们只使用最简单的方法。
BuilderD 表
让我们进一步了解生成器定义表BUILDERD.DBF,因为理解它的结构是建立你自己的生成器的关键。表2 显示了BUILDERD.DBF表的结构; 在该表中,"property control" 意思是生成器中的、维护一个目标对象的指定属性的控件。
| 字段 | 用途 |
| TYPE | 定义记录类型。它包含了两个内容:如果这是一个类记录它是 “ CLASS ” ,如果这是一个属性记录则它是 “ PROPERTY ” 。 |
| ID | 记录识别符,通常是类的名字。 |
| LINKS | 连接到该记录的其它记录的 ID 值列表 ( 以回车符分隔 ) 。该字段的更多细节在下面描述。 |
| TEXT | 如果这是一个 “ CLASS ” 记录,则为生成器表单的标题提示。 如果这是一个 “ PROPERTY ” 记录,则为属性控件的标题提示。 |
| DESC | 属性控件的状态条文本。 |
| CLASSNAME | 对于 “ CLASS ” 记录,是类的名字。 BuilderDB 搜索该字段中指定的、一个包含目标对象类的记录。 对于 “ PROPERTY ” 记录,是要为属性控件实例的类的名字。若该字段为空,将使用 BUILDERD.VCX 中的默认的类 ( 逻辑属性是 BuilderCheckBox 其它属性是 BuilderTextBox) 。你指定的任何其它类都必须是 BuilderD 类的子类,因为这些类具有被 BuilderDForm 使用的特殊的属性和方法。 |
| CLASSLIB | 包含有 CLASSNAME 中指定的类的类库。该属性可以包含一个表达式 ( 例如 HOME() + “ WIZARDS\BUILDERD.VCX ” ) 或一个常量 ; 在这种情况下,在表达式事放上方括号。对于 “ PROPERTY ” 记录,如果该字段为空且指定了 CLASSNAME ,则假定为 BUILDERD.VCX 。对于 “ CLASS ” 记录, BuilderDB 搜索一个包含有与该字段内容值相同的记录 ( 如果必要的话,在求值后 ) 作为目标对象的 ClassLibrary 属性,保留该值为空可建立一个类的生成器而不必担心它是在哪一类库中。 |
| MEMBER | 对于 “ CLASS ” 记录,其值为空。对于 “ PROPERTY ” 记录。如果为空, ID 必须包含属性名。 |
| HELPFILE | 包括有该类的帮助内容的 CHM 文件名。如果为空,就使用当前帮助文件。 |
| HELPID | 帮助主题 ID 。 |
| TOP | 属性控件的 Top 设置。如果为 0 , BuilderDForm 将该控件放在先前一个控件的下面 ( 页面上的第一个控件放置在 BuilderDB.nTop 属性中指定的位置 ) 。 |
| LEFT | 属性控件的 Left 设置。如果为 0 , BuilderDForm 将该控件放在先前一个控件的左边 ( 在 BuilderDB.nLeft 中指定 ) 。 |
| HEIGHT | 属性控件的 Height 设置。如果为 0 ,就使用该控件的默认 Height 值。 |
| WIDTH | 属性控件的 Width 设置。如果为 0 ,就使用该控件的默认 Width 值。 |
| ROWSRCTYPE | 如果类使用的属性控件 ( 在 CLASSNAME 中指定 ) 是一个 combobox ,该 combobox 的 RowSourceType 设置。 |
| ROWSOURCE | 如果属性控件是一个 combobox ,该 combobox 的 RowSource 设置。例如,如果 ROWSRCTYPE 是 1 ( 值 ) , ROWSOURCE 将包含一个用于 combobox 的、以逗号分隔的值的列表。 |
| STYLE | 如果属性控件是一个 combobox ,该 combobox 的 Style 设置。 |
| VALIDEXPR | 一个用于验证属性值的表达式。 |
| READONLY | 如果属性控件中只读的则为 .T. 。 |
| UPDONCHNG | 如果属性控件的值写到目标对象的属性后被修改,则为 .T. ( 就是说,交互式方式 ) 。 |
| UPDATED | 决定记录最后修改 ( 未被 BuilderD 使用,仅是一个信息 ) 。 |
| COMMENT | 关于记录的注释 ( 未被 BuilderD 使用 ) 。 |
| USER | 用户信息记录 ( 未被 BuilderD 使用 ) 。 |
表 1. BUILDERD.DBF的结构
表2 和 3 显示了两个生成器的记录,VFP6.0的基本类_HyperLinkBase 和_HyperLinkLabel 类。我没有显示BUILDERD.DBF 表中的所有字段,出于空间的考虑; 仅列出与讨论有关的字段。
| TYPE | ID | LINKS | CLASSNAME | CLASSLIB |
| CLASS | _HyperLinkBase | Ctarget cFrame lNewWindow | _HyperLinkBase | (HOME()+"FFC\_Hyperlink.vcx") |
| CLASS | _HyperLinkLabel | Caption _HyperLinkBase | _HyperLinkLabel | (HOME()+"FFC\_Hyperlink.vcx") |
表 2. _HyperLinkBase 和 _HyperLinkLabel 类的生成器记录
| TYPE | ID | TEXT | CLASSNAME | ROWSRCTYPE | ROWSOURCE |
| PROPERTY | cTarget | Target URL : | BuilderComboBox | 1 | www.microsoft.com/vfoxpro |
| PROPERTY | cFrame | Frame : | |||
| PROPERTY | lNewWindow | Open new browser window | |||
| PROPERTY | Caption | Caption |
表 3. _HyperLinkBase 和 _HyperLinkLabel 生成器管理的属性定义记录
在表2 中的 _HyperLinkBase记录中,我们看见CLASSNAME 和 CLASSLIB 指定了该生成器使用的类(注意CLASSLIB 包含了一个在运行时求值的表达式而不是一个硬编码值),在LINKS 中列了出该生成器可管理的类的属性。显示在表3中的cTarget PROPERTY 记录指明该属性将被BuilderComboBox 控件管理,且该BuilderComboBox的RowSource 包含一个 Microsoft VFP 网站的URL (目前是msdn.microsoft.com/vfoxpro)。cFrame 将被 BuilderTextBox 对象管理(因为它是一个字符串属性且该类没有定义),lNewWindow 将有一个BuilderCheckBox 对象(因为它是一个逻辑属性)。
看起来是如此简单,对吧? 好了,LINKS 字段事实上可以更加复杂。首先,假如在LINKS字段中指定的类记录的ID 没有匹配记录,该ID 就被假设为属性名。
这样,你可以简单的在CLASS记录的LINKS字段中指定它管理的属性来实际建立一个生成器。当然,你必须保持属性的 captions 和属性的名字相同,没有状态条文本、默认的类和属性大小,但这些对于一个快速的和dirty 生成器来说都不算太坏。
第二个复杂之处是,CLASS记录的LINKS字段中指定的ID 可以指向另一个CLASS 记录而不是一个PROPERTY 记录。在这种情况下,该类 "继承"所有指定类的连接。你可以在表2中的_HyperLinkLabel 记录中看到这一点; 它的一个连接是_HyperLinkBase,因此它不仅用该类的生成器来管理Caption 属性(specifically 在它的 LINKS 字段中列出),同时也管理cTarget、cFrame和lNewWindow 属性,因为这些在_HyperLinkBase 的LINKS 字段中指定。
第三,PROPERTY 记录可以连接到另一个PROPERTY 记录。在这种情况下,记录"继承"连接记录的所有非空字段。最后,如果你用@<caption> 格式指定它,LINKS 可以包含生成器中的页框的标题 (例如,@Properties 使用"Properties" 作为页的标题)。
创建一个生成器
让我们在TEST.VCX类库中创建一个名为TestCheck的 VFP CheckBox 的子类作为开始。在该类中添加一个叫做BuilderX 的自定义属性并设置它的值为= HOME() + "Wizards\BuilderD,BuilderDForm"。然后在该类上右击鼠标并从快捷菜单中选择 生成器 。我们得到一条"没有注册该类型的生成器"的错误。这也说得通,因为我们还没有定义它 (尽管这令人不愉快,在稍后它将自动为我们建立一个)。按以下步骤做:
- 在命令窗口中打入USE HOME() + "WIZARDS\BUILDERD",然后打入BROWSE。
- 从表菜单中选择"添加新记录"。
- 在TYPE字段中输入"CLASS",ID 字段中输入"TestCheck",LINKS 字段中输入"Enabled","AutoSize"和"Caption" (在输入各字段后按回车键),TEXT 字段中输入"My Test Builder",CLASSNAME 字段中输入"TestCheck",CLASSLIB 字段中输入 "TEST.VCX"。
- 关闭浏览窗口并在命令窗口中打入USE 来关闭该表。
- 在TestCheck 上右击鼠标并选择生成器。
酷不酷,嗯? 完全是你自己的生成器,仅在一分钟左右就建立了! 去掉Enabled 的选择并输入一个不同的Caption,并可看到这些属性已被修改。
注意不要被为属性建立记录所迷惑; Enabled 和 Caption 记录已存在于 BUILDERD.DBF表中,因此我们只需重用它们,而且由于不存在 AutoSize 记录,BuilderD 只假设我们想管理那个属性。
让我们再建立一个并看看以多小的输入可以得到一个可工作的生成器。在TEST.VCX类库中创建一个名为TestText的 VFP TextBox 的子类,向它添加一个属性 BuilderX 并设置它的值为 = HOME() + "Wizards\BuilderD,BuilderDForm"。在BUILDERD.DBF 中添加一条记录并只指定 TYPE ("CLASS"),ID ("TestText"),LINKS ("ReadOnly")和 CLASSNAME ("TestText";我们可以跳过 CLASSLIB 但 CLASSNAME 是必须的)。关闭该表,然后调出该类的生成器。
这也是一个可以工作的生成器。单击生成器中的 生成器 按钮; 它调出另一个生成器(如果Builder属性存在并非空,在Builder属性中指定的生成器,否则就是该类的基类的默认生成器,在此例中是 VFP TextBox 生成器)。这样,即使我们可以为一个类指定生成器,只要愿意,我们仍然可以访问更多的普通生成器。
预置-内建的生成器
我最近用BuilderD 建立了两个生成器,一个为我的SFGrid 类另一个为SFPageFrame。SFGrid 生成器维护DeleteMark 和RecordMark 属性(你可以很容易地添加其它你可能经常修改的属性) 但由于它们容易在属性窗口中修改,所以这不是我要建立该生成器的真正的理由。真正的理由是因为我喜欢使用grid 的columns 中的SFGridTextBox 对象(SFTextBox 的一个子类,具有一小部分不同的属性设置,以提供它们在 grid中出现的外观),并尽量用SFGridTextBox对象替换一般的TextBox 对象。
因此我建立了一个按钮(SFBUILDERS.VCX类库中的SFGridTextBoxButton类) 来自动进行该工作。以下是该按钮的 Click 方法代码:
| local loColumn, ; loControl, ; lcName for each loColumn in Thisform.oObject.Columns for each loControl in loColumn.Controls if upper(loControl.Class) = 'TEXTBOX' lcName = loControl.Name loColumn.RemoveObject(lcName) loColumn.NewObject(lcName, 'SFGridTextBox', ; 'SFCtrls.vcx') endif upper(loControl.Class) = 'TEXTBOX' next loControl next loColumn wait window 'SFGridTextBox added to each column' ; timeout 2 |
BuilderDForm 有一个引用到目标对象保存在它的 oObject 属性,因此生成器表单上的许多控件可以引用或修改目标对象的东西。在这些代码中,Thisform.oObject.Columns 引用grid 的Columns 集合被生成器影响。
这里有一个副作用:我怎样才能在我的生成器表单中得到该按钮? 我可以为我的生成器在BUILDERD表中建立一个记录,但那会添加按钮到页框中的一个页上,而我确希望该按钮象其它的生成器按钮一样出现。
因此我建立了一个 "button loader" 类 (SFBUILDERS.VCX类库中的SFBuilderButtonLoader类),该类添加一个按钮到生成器表单然后返回 .F. 这样,它实际上没有实例化。SFBuilderButtonLoader 在BUILDERD表的当前记录的备注字段 USER 中查找(使用类实例化的记录) 要添加到表单的按钮的类名和类库(注意下面代码中的 BUILDERD 是以 "BUILDER"别名打开的)。对于SFGridTextBoxButton,例如,我在BUILDERD表中建立一条记录,以 "SFGridTextBoxButton" 作为它的 ID 但 "SFBuilderButtonLoader" 是一个类,并在USER字段中输入 "SFBuilders, SFGridTextBoxButton"。这告诉 SFBuilderButtonLoader 添加一个 SFGridTextBoxButton 到表单中。以下是SFBuilderButtonLoader的 Init 方法代码:
| local lcClass, ; lnPos, ; lcLibrary, ; lnTop, ; lnLeft, ; loControl * 如果 BUILDERD 表已经打开且定位在要载入的按钮记录上 |
在BUILDERD表中 SFGrid 记录的LINKS字段中有 DeleteMark、RecordMark 和 SFGridTextBox,因此该生成器获得这些控件。
现在我可以为我的SFGrid对象调出我的BuilderD 生成器,使用 VFP Grid 生成器(单击BuilderD表单上的 生成器 按钮) 来快速建立 grid中的列,然后单击我的 添加SFGridTextBox 按钮来让这些列使用 SFGridTextBox 对象。
对于 SFPageFrame,我也希望相似的功能:一个添加代码到页框中各页的RightClick方法的按钮; 这允许在页面上右击鼠标明时为页框或整个表单提供快捷菜单。象SFGrid 生成器一样,我在BuilderD表中建立了一个记录来实例化一个 SFBuilderButtonLoader 对象,然后添加一个 SFCodePageButton 对象到生成器表单。以下是添加代码到各页的SFCodePageButton的Click方法:
| local lcCode, ; loPage, ; lcCurrentCode lcCode = 'This.Parent.ShowMenu()' + chr(13) for each loPage in Thisform.oObject.Pages lcCurrentCode = loPage.ReadMethod('RightClick') if not lcCode $ lcCurrentCode loPage.WriteMethod('RightClick', lcCurrentCode + ; iif(empty(lcCurrentCode), '', chr(13)) + lcCode) endif not lcCode $ lcCurrentCode next loPage wait window 'Code added to each page' timeout 2 |
要添加这些新的生成器到你的系统中,复制 SFBUILDERS.VCX 和 VCT (在本文档的源代码文件中可以找到) 到一个目录,然后打开示例文件提供的NEWBUILDERS 表,并修改 CLASSLIB 字段到包含路径的 SFBUILDERS.VCX文件名。打开 BUILDERD 表,然后 APPEND FROM NEWBUILDERS 表,添加Builder 和BuilderX 属性到你的类中,并在BuilderX中输入 = HOME() + "Wizards\BuilderD,BuilderDForm"。
其它生成器类
我早些时候注意到,SFBUILDERS.VCX 中的BuilderCheckBox 和 BuilderTextBox 类都是派生自同一个BuilderD 类。在附加的右击鼠标行为中,这两个类具有nTop 和 nLeft 属性,当这些属性改变时,用assign 方法设置它们的自定义的lMoved 属性为.T.。这允许我们查觉到什么时候一个属性控件被移动了(在属性控件生成器中的Left 和Top 是绑定至 nLeft 和 nTop 而不是直接绑定到 Left 和 Top)。只有当一个属性控件移动时,我们保存它的its Left 和 Top 值到 BUILDERD 表。 SFBUILDERS.VCX 中的加一个类是 SFPropertyCaption。该控件用于管理属性控件的 caption。
为什么用一个特别的类来做这件事? 为什么不用一个普通的BuilderTextBox 对象?
理由是如果属性控件是一个BuilderCheckBox,它有一个 Caption 属性,因此控件管理该属性。但是,如果属性控件是一个 BuilderTextBox,它没有 Caption 属性但确有一个组合的 label 对象,因此控件必须管理对象的对象的 Caption 属性。由于 BuilderD 类只管理单个对象的属性,SFPropertyCaption 不得不超越(override)少量方法来允许它处理这种情况。
SFBuilderPropertyComboBox 类是BuilderComboBox的一个子类。它提供一个目标对象的所有可写属性的一个列表(用 AMEMBERS() 得到所有属性的列表然后检查各自的PEMSTATUS()来排除只读的)。虽然它的主要目的是修改属性控件的 cProperty 属性(用于检查要管理的属性),它有两个有趣的行为。
首先,如果属性控件是一个BuilderTextBox 但你只修改了它管理的逻辑属性(如 Enabled),它移去BuilderTextBox 及组合的label 对象,并放一个 BuilderCheckBox 在这个地方。如果你改变了一个逻辑属性为另一个类型它会做相反的事。
第二,如果你输入了一个不存在的属性名,你会被提示建立该属性。如果你同意,生成器使用AddProperty 来添加新属性到目标对象。我的确不推荐在一个拖放到表单中的对象上这样做,因为这等于实例化编程; 如果你真的需要一个新属性,你可以考虑用子类来代替。
总之,这是一个经一个步骤在类中建立新属性并用生成器管理它的快速方法。 SFBuilderAddButton 是一个为了属性控件使用 SFBuilderButtonLoader 类而添加到生成器表单的按钮类 (派生自BuilderCommandButton)。SFBuilderAddButton 的Click方简单地调用属性控件所在的生成器表单的 AddPropertyControl 方法来建立一个新的属性控件,然后转换当前生成器表单来管理新属性控件。
这意味着你可以单击 添加属性按钮 添加一个新属性到生成器,并在生成器中管理它。这是一种添加多个属性控件的快速方法。
试一试
让我们看一看SFBuilderBuilderForm; 它实际上比对它的描述更易于使用。在TEST.VCX中建立一个叫做 MyTestText 的VFP TextBox 类的子类。添加一个 BuilderX 属性并设置它的值为 "SFBuilders,SFBuilderBuilderForm",然后调出该类的生成器。
注意尽管我们没有在BUILDERD 表中建立任何记录,我们仍然得到了一个生成器表单(当然,表单上没有控件,但我们马上会改变这一切)。
这是因为 SFBuilderBuilderForm 在不能找到该类的记录时,自动地为该类在BUILDERD表中建立了一个 CLASS 记录。
单击 添加属性控件 按钮。你会注意到一个文本框和标签出现在生成器表单上,但随后另一个生成器表单出现在该生成器表单的面上。这个新的表单是我们刚添加的属性控件的生成器。在属性 combobox中,选择 "Tooltiptext" 并修改 Caption 为 "Tool Tip Text" 和 Width 到250。移动该生成器表单在一旁并注意原表单上的属性控件也同样修改了。
单击第二个生成器表单上的 添加另一属性 按钮并注意另一个属性控件已添加到原生成器表单中,并且该生成器表单现在可以管理它了。为属性选择"Statusbartext" 并修改Caption 为"Status Bar Text" 和Width 到250。添加另一个属性控件,并为该属性选择 "Readonly" 并修改Caption 到"Read-Only"; 这时,注意原生成器属性控制改变了checkbox。关闭新的生成器表单。图3 显示了我们建立的生成器,图4 显示了属性控件生成器。
图 3. 我们为MyTestText 对象建立的BuilderD 生成器
要修改控件的属性,在控件上右击鼠标并从快捷菜单中选择修改属性修改控件; 你以前看到过的相同的属性控件生成器会出现。要移去属性控件,从菜单中选择 移去属性控件 。要重置目标对象的该属性的值为默认值,选择 重置为默认值 。
让我们修改生成器表单的标题为更为合适的东西。单击 修改生成器标题 按钮并在随后出现的SFBuilderBuilder生成器表单中输入 "我的测试文本框类生成器" 。关闭第二个生成器。
如果你在关闭生成器时没有保存,下一次你再调用 MyTestText 对象的生成器时,你的设置不会起作用。若要保存生成器的定义到 BUILDERD 表中,单击 保存生成器 按钮。如果你想导出生成器定义到另一个表中,单击 导出生成器 按钮并在出现的对话框中输入文件名。然后你可以发送该表给一些人,他们可以将其导入他们的 BUILDERD 表并访问你建立的生成器。
说明
因为VFP的开放式IDE,任何人都可以很容易地定制它们的开发环境,因此他们和他们的开发组可以极大地提高编程效率。
Tags:
作者:佚名评论内容只代表网友观点,与本站立场无关!
站长推荐
栏目导航
本类热门阅览
相关文章
- › 使用 Visual FoxPro 的Slider ...
- › 使用 Visual FoxPro 的Progres...
- › 使用 Visual FoxPro 的Calenda...
- › 使用 Visual FoxPro 的Common ...
- › 使用 Visual FoxPro 的TreeVie...
- › 使用 Visual FoxPro 的 ImageL...
- › 使用 Visual FoxPro 的 Active...
- › foxpro 结论
- › foxpro 维护源表
- › foxpro 多个本地数据
- › foxpro 机动查询和数据输入
- › foxpro 刷新(Refreshing) 离线...
