CA资料-我眼中的 Visual FoxPro 8 [转帖]

    技术2022-05-11  2

    本文已在《程序员》杂志 2003 第一期发表   鸣谢 经常来 BOE 的朋友都知道,我早就是一个业余的 Foxer 了,随着时间的推移,每一次写东西都是惴惴不安的,真的担心弄出一些误导言论或者是贻笑大方。 每一次完成初稿我都会给一些朋友预览,好听听他们的建议。漫步者、Boby、将来是我 这三位网友就是我的第一批读者,感谢他们一次又一次读那些晦涩的言语、理解不成熟思想。特别是漫步者,几乎成了我的私人秘书,就拿这篇东西来说,正文中所有的小标题都是他加上的。他说,这样能让读者容易看懂…… 成文后,属上的都是我的名字,真是有白占别人劳动的嫌疑,这里向这些朋友表示深深地感谢! 写在开始 那一日,我连夜等待 Visual FoxPro 8下载,直到凌晨2点,实在是熬不下去,这才睡下;4点时分,被闹钟催醒,睡眼朦胧中看到了 Visual FoxPro 8 的下载连接,迷迷糊糊的也知道动作是怎样完成的,反正早上醒来 Visual FoxPro 8 已经在我的硬盘上了。解压、安装,一阵兴奋难以言表;忽又想起,还要上班,于是烧盘、走路……这样,开始了我的 Visual FoxPro8 探索之旅。 原来以为,已经看过大量原版资料(感谢 夜来香、RMH、Fbilo 他们已经翻译这些东西,英文不好的朋友现在也能享用了),我研究Visual FoxPro 只需要少量的精力。嘿嘿,谁知道:我竟然花费了整整10天才看完了我所感兴趣的东西。而且是每天作战到凌晨,家人意见不少,但是他们哪知道我乐在其中呢? 今年10月,微软公司如期发布了 Visual FoxPro 8b 版,这是自 Visual FoxPro 3 以来又一个精彩的版本,一下子吸引住了全球 Foxers 的眼球!下文是我学习、探索 Visual FoxPro 8b的一些感受,愿与读者分享。 (一)结构化异常处理 经常有人问我,你认为 Visual FoxPro 最需要改进的地方在那里。我总是不假思索地回答:异常处理!期盼它能拥有结构化异常处理机制,把异常及其处理限定在最小的范围之内,使代码的封装更优。 Visual FoxPro 8 以前对异常处理还限于全局 On Error 例程和对象的 Error 的事件,它们都有这样一个毛病:一旦出现了异常,程序就必须跳出正在执行的程序模块,执行异常处理例程,这时发生异常前执行的程序模块的上下文已经丢失殆尽,系统很难调整到正常状态或者给调用接口一个明确的答复! Visual FoxPro 8的 Try…Catch…Finally…End Try 结构让人激动不已,它帮助我们方便的抓住“异常”,实时针对各种异常做出反应,缩小异常的影响范围,保护系统的运行状态;也使得程序模块的封装性更好。想想以前应付“异常”而设计的各种方案,一夜之间成了昔日黄花、显得那样苍白无力,真是“沧海变桑田”啊! 仅此一项增强,Visual FoxPro 8就已经光彩夺目,相信任何开发人员都经不起“结构化异常处理”的诱惑,升级到Visual FoxPro 8! (二)CursorAdapter 类 Visual FoxPro 8提供的CursorAdapter 类也是大家所津津乐道的。CursorAdapter 是一个基于松散耦合思想设计的对象化的 Cursor 处理模型。 1、VFP8以前的数据处理 在 Visual FoxPro 8 之前,我这样评价在 Visual FoxPro 里的数据处理:灵活而强健,面向记录处理为主、面向集合处理为辅,采用过程化程序化模型,但可以利用面向对象的 XBase 语言自行封装数据处理对象。 说到Visual FoxPro 处理数据的灵活和强健,归根到底是Visual FoxPro 同时支持 XBase语言和SQL 语言。XBase 语言善于对记录的梳理,这种处理往往是基于“行”的模式;SQL 语句对数据的处理是根据“集合”的概念,按照条件取得“集合”,然后处理。无论采用 XBase 语言处理数据,还是使用 SQL语言,Visual FoxPro 预设提供面向过程的数据处理程序化模型,而不是流行的面向对象的程序化模型。(当然,Visual FoxPro 并不限制开发人员编写自己的数据处理对象。) 那么在 Visual FoxPro 中所谓的“数据”到底是什么呢?是 Cursor!在Visual FoxPro 中整个数据处理是围绕着 Cursor 进行的(而不是 DBF),所以对象化数据处理模型也应该从 Cursor 着手! 2、面向对象的数据处理器 在 Visual FoxPro 设计对象化的 Cursor 是没有太大的意义的,这句话很突兀、让人摸不到头脑! 我们用典型的对象化游标──ADO 组件的 RecordSet 做分析。可以发现RecordSet 中定义了数据集合的来源、数据集合的结构(表结构)、数据的更新回送方式,另外RecordSet还能够对数据集合进行操作,诸如Move、Find、Delete之类的方法。 在 Visual FoxPro 里对象化 Cursor,是不是要照搬 RecordSet 的一套呢?如果由我们来设计这个构架,我们如何取舍呢?我觉得没有必要实现对 Cursor 本身的封装,这样是在自残,我刚才已经说过了:整个 Visual FoxPro 对数据的处理就是对 Cursor 的处理,如果封装了Cursor,就背离了整个体系的构架思路,同时也放弃了 Visual FoxPro 对数据处理的“灵活与强健”的特色。这就是前面提到的“在 Visual FoxPro 设计对象化的 Cursor 是没有太大的意义的”。 对象化 Cursor 没意义,那么我们究竟需要什么呢? 用一个面向对象的 Cursor 的处理(管理)器来管理 Cursor的数据来源、Cursor 的数据结构、Cursor 中数据变动的更新回送;而Cursor 依旧是传统意义上的 Cursor,没有任何改变,依旧可以用 XBase 或者 SQL 语言对直接 Cursor 进行处理──这才是我们需要的。 说到这里,大家会问了,这不就是DBC 中的视图吗?它们在实现的功能上确实有点像,但实质上是有差别的: 首先,视图是 DBC 的对象(组件),利用视图设计的系统对 DBC 的依赖性大,不符合多层体系构架的思路。而我们的对象化 Cursor (处理)管理器与 DBC 无关,完全是程序设计级别上的概念。从多层体系设计的观点看,属于业务逻辑层次。 再者,视图是数据库中的概念,灵活性差、可控制性差;而对象化 Cursor (处理)管理器就不一样了,作为对象,具备了面向对象程序设计的一切好处,很容易定义、改变、维护,更能够继承使用,这都是使用视图所办不到的。 随便就能举出几个视图弱势的例子,有时候数据集合的结构一致,但是数据来源不一样,就必须麻烦的设计几个视图,而使用对象化 Cursor (处理)管理器就只需要改变数据来源属性;又例如,数据来源相同、数据集合结构相同,只不过获取数据的条件不同(Where 子句构造不一样),用视图就必须设计新的视图,而使用对象化 Cursor (处理)管理器只需要改变相关属性即可…… 说到底:视图是数据库的概念,具备的是数据库对象(组件)的特性,而不具备程序化语言的特点,对象化 Cursor (处理)管理器解决的就是这个问题。 3、松散耦合 在视图时代或者是RecordSet时代采用的是紧密耦合的思维,说的直白一点,就是:数据从哪里来就更新到哪里去,程序员并不能在其中做什么干涉,一旦执行了TableUpdate(),就自动更新数据源,没有商量的余地! 松散耦合提供了一种开放的模型,它以 Cursor 为中心,从本质上认识到 Cursor 与资料源的相互关系是:数据采集和变动更新回写。数据采集就是 SQL-Select操作、数据更新回写就是SQL-Insert、SQL-Update、SQL-Delete。松散耦合模型提供 Cursor 数据来源的Select子对象、Cursor 数据添加回写数据源的 Insert子对象、Cursor 数据修改回写数据源的 Update 子对象、Cursor数据删除回写数据源的 Delete 子对象。由于从构架上把Cursor与数据源的各种互动操作分割开来,再辅以事件模型,这就给了开发人员很大的程序化余地! 相比之下,视图虽然也采用类似的思路与数据源互动,但是它把整个互动过程作为一个整体看待,严严实实的包装起来,造成开发人员的参与性差。 4、VFP8的CursorAdapter对象 让我们回过头再看 Visual FoxPro 8 提供的 CursorAdapter 对象,预设情况下支持四种数据来源,分别是:Native(本地资料)、ADO、ODBC、XML。请思考一下,为什么 CursorAdapter 能够支持几种性质完全不同数据来源呢,原因就是采用了松散耦合模型,让开发人员用自己的代码参与资料的获取,使得整个体系的扩展性大大增强,这就是松散耦合的魅力! 概括VFP8中的CursorAdapter,它是:一个基于松散耦合思想设计的对象化的 Cursor 处理模型。CursorAdapter 既保留了 Visual FoxPro 对 Cursor 处理的传统优势,又引入了先进的程序化模型和构架。再次强调它的特色: Cursor 本身没有被对象化,依旧是传统意义上的 Cursor,可以使用一切传统语句处理 Cursor。 Cursor 的管理方式对象化了,可程序化性更好,有利于代码的封装。 采用了松散耦合的设计思维,以 Cursor 为中心、但又分割了 Cursor 与数据源之间的各种操作、再加入事件模型,为开发人员提供了很多参与的机会。   (三)全新的“连接” Visual FoxPro 8在异构数据库程序化的增强,更直接一些就是对 SQL Server支持的改进。我觉得,至少体现在两个方面:一个是通过 CursorAdapter 对象提供一种比远程视图更加灵活、更容易程序化的 Cursor 管理对象;另一个是在原有的基础上革新了“连接”的概念,使得“连接”处理更加独立、“连接”管理更加方便、“连接”更容易被共享! 前面我们已经对CursorAdapter作了分析,接下来就让我们看看“连接”的变化吧! 1、DBC 中的“连接”对象和“连接句柄” 以前我介绍 Visual FoxPro 里的“连接”时,经常强调这样两个概念:DBC 中的“连接”对象(组件)和“连接句柄”。 DBC 中的“连接”对象(组件)只是一种定义,描述怎样通过 ODBC 连接到数据源;“连接句柄”是实例化的“连接”对象(组件)。我们可以通过USE远程视图或者使用 SQLCONNECT() 打开“连接”对象,得到“连接句柄”。 当然这里还有两个更加直接的方式得到“连接句柄”,就是在SQLSTRINGCONNECT()中直接使用连接字符串;还有就是SQLCONNECT()中直接引用操作系统(ODBC.INI)中的DSN 定义。这里需要注意的是,这两种方式跳过了 DBC 的“连接”对象(组件),使得Visual FoxPro 不容易管理它们。也许这个问题在 Visual FoxPro 8 以前还不很明显,但 Visual FoxPro 8 以后应该慎用它们了。 Visual FoxPro 8 以前,“连接句柄”并不独立,主要是“远程视图”自己管理“连接句柄”,其它“远程视图”或者 SPT需要共享“连接句柄”是非常麻烦的,“远程视图”之间共享“连接句柄”,需要定义所有参与共享的“远程视图”的ShareConnection属性为.T.;SPT 要取得“远程视图”的“连接句柄”必须在USE“远程视图”以后,使用 CURSORGETPROP() 函数取得“连接句柄”。除了麻烦以外,还有不能实现的功能,如先期已经存在的“连接句柄”不能被“远程视图”共享。这一切都是因为“远程视图”太过“自主”所致! 2、Statement Handle Vs. “连接句柄” 为了解决“远程视图”的“独断专行”,Visual FoxPro 8 提出了“远程视图”设计时与运行时分离的思路,引入了一个新的关于“连接”的概念── Statement Handle,同时 Visual FoxPro 8 保留了“连接句柄”概念。 从 Visual FoxPro 8 开始开发者直接处理的任何Handle 都是 Statement Handle,而不是“连接句柄”了!进一步讲,“远程视图”使用的是 Statement Handle、SPT使用的也是 Statement Handle。也就是说,我们在 DBC 里建立、设计“远程视图”使用的连接与“远程视图”运行时的连接可以没有任何关系,我们可以在 USE 语句中实时指定“远程视图”将要使用的Statement Handle。这就是 Visual FoxPro 8 针对 C/S 程序化中“连接”的一个改进。 Statement Handle 与“连接句柄”的关系。我以为: ? “连接句柄”是 Statement Handle 的基础,Statement Handle 是“连接句柄”的衍生; ? 真正与数据源连接的是“连接句柄”,而不是Statement Handle;开发人员直接操作的是Statement Handle,而不是“连接句柄”; ? 某一条“连接句柄”可以衍生出一条或者多条Statement Handle,而某一条 Statement Handle只可能对应到一条确定的“连接句柄”; ? 新建一条连接的实例,将(可能)产生一条新的“连接句柄”和一条新的Statement Handle,这条Statement Handle对应到这条“连接句柄”; ? 新建一条连接的实例,如果当前Visual FoxPro 系统中已经存在一条符合以下两个要求的“连接句柄”,这两个要求分别是允许被共享,并且“连接”语句的定义一致(可以认为来源于 DBC 的同一个“连接”对象),这时不会生成一条新的“连接句柄”(不会再与数据源建立一条新的 ODBC 通道),而是直接由已经存在的“连接句柄”衍生出一条新的Statement Handle,这条Statement Handle共享原先那条“连接句柄”。 ? 当一条“连接句柄”衍生的 Statement Handle 全部被释放了以后,“连接句柄”才会释放。 ? 能够衍生出多条Statement Handle的“连接句柄”一定是根据 DBC中“连接”对象(组件)所创建的。使用连接字符串(SQLSTRINGCONNECT())或者直接引用操作系统 DSN 创建的“连接句柄”只能衍生出惟一条Statement Handle。这是因为,Visual FoxPro 判断不同 Statement Handle是否“同源”的依据是 DBC 的“连接”对象(组件),而不是 ADO.NET那样根据连接字符串定义区分。 从上面的表述中,我们发现:因为“连接句柄”与 Statement Handle 的关系是“一对多”的,如果拿 SQL Server 作为资料源,我们可以建立一条“连接句柄”、根据它衍生多条Statement Handle,实际上连接到 SQL Server 的只有一条连接,而应用程序中却可以有多条Statement Handle,这就是一种新概念的“连接共享”! 3、一个实例 让我们通过下面的例子来具体了解一下Statement Handle与“连接句柄”。设DBC里有一个“连接对象”(组件),名为 “CD1”,连接到SQL Server 数据库。 Con1=SQLCONNECT("CD1",.T.) 连接成功,返回 Con1=1。Con1 表示 Statement Handle ,以后就可以通过这个Con1 打开“远程视图”或者执行 SPT 命令;由于此时系统中并没有存在“连接句柄”,所以同时产生一条“连接句柄”,需要获取“连接句柄”可以通过: ODBC1=SQLGETPROP(Con1,"ODBChdbc") 我这里ODBC1=49551184。这个ODBC1并不需要开发人员关心,也不需要通过程序维护。 接着,我们就根据 ODBC1衍生其它的Statement Handle: Con2=SQLCONNECT("CD1",.T.) 返回 Con2=2。Con2 表示 Statement Handle,以后就可以通过这个Con2 打开“远程视图”或者执行 SPT 命令;我们获取 Con2 的“连接句柄”看一看: ODBC2=SQLGETPROP(Con2,"ODBChdbc") 我这里ODBC2=49551184。说明,ODBC1和 ODBC2 是一个东西,衍生了两条Statement Handle。虽然,程序化中,我们可以使用 Con1或者 Con2,但是真正与 SQL Server 的连接只有一条,就是“连接句柄”。 接着,我们需要关闭“连接句柄”衍生的所有的Statement Handle,才能真正断开Visual FoxPro 与 SQL Server 之间的连接。 SQLDISCONNECT(Con1) SQLDISCONNECT(Con2) 通过这个简单的例子,也许有利于我们来理解新的“连接”。虽然,Visual FoxPro 8在“连接”这一块动了大手术,但是这对于旧的系统影响并不是很大,因为程序化接口并没有变化,Visual FoxPro 只是在更底层的地方加了一层概念。话要说回来,如果要真正用好这个新特性,还是应该改变一下程序化思路。 4、 “连接”体系更改的原因 也许,我们还会有这样的疑问:Visual FoxPro 8为什么要加入 Statement Handle 这样的概念,按照以前的做法直接使用“连接句柄”在各处传递,不是也能够实现“连接共享”吗? 笔者认为这是面向对象化程序化的需要、多层开发的需要。整个构架系统的思路就是更好的面向对象化程序化、更好的多层开发,所以才有了结构化错误处理、CursorAdapter和此处的 Statement Handle。 上文,我使用了“衍生”一词,从词义理解一定是用“连接句柄”显式生成新的Statement Handle,但是从实例代码中大家看到了,产生 Con1和 Con2 的代码是一样的。从理论上讲,就是:Visual FoxPro 能够自行管理 “连接句柄”,开发人员只需要也只应该处理相应的 Statement Handle,而并不需要关心由同一“连接句柄”衍生出的其它Statement Handle。这样对于模块化开发是很有好处的,同时也解决了“共享连接”问题。 关于“连接”的话题谈的差不多了,不过有一个问题是需要注意的:一个“连接句柄”可以衍生多个Statement Handle,在开发中,如果有一个 Statement Handle进入了事务处理状态,这时所有相关的Statement Handle也进入了事务处理状态,这因为 SQL Server 的事务是根据真实的连接区分的——由于某一条Statement Handle 的要求,使得“连接句柄”进入了事务状态,理所当然的该条“连接句柄”衍生的所有Statement Handle也一起进入了同一个事务处理。(当然,不是由已经进入事务状态的“连接句柄”衍生的Statement Handle,不会进入事务状态。) 终于完成了我在 Visual FoxPro 8 中的“首航”,细细揣摩一些 Visual FoxPro 8的新特征,我深深体会到一个指导思想贯穿着整个 Visual FoxPro 8的构架,这个思想就是:支持更完美的对象封装。由此推断:用 Visual FoxPro 8 进行对象化程序化、多层开发一定比以前更加容易、更加灵活! 本文只涉及到 Visual FoxPro 8 的三个新特性,事实上还有更多令人愉悦的东西没能讲到,希望以后能有机会与大家共同探讨!大家可以到:   三思同志 发表于2005-04-23 7:56 AM  IP: 218.15.22.* 强大的报表设计器 Visual FoxPro 9.0 的报表设计器(1)  Micorsoft公司对新推出的Visual Foxpro 9报表设计器作了显著地改进,同时又与老版本的Visual Foxpro保持了向后兼容性,新版本的报表设计器是一个新旧版本的混合体。   在本文中,你将了解报表设计器对新的数据环境、报表保护、用户界面、对象布局与数据分组功能的增强。最后,你将了解Visual FoxPro 9报表设计器的一个最有用的增强功能:多条明细区带(multiple detail bands)。  报表设计被一个新增的“Xbase报表设计器”的工具代替。它提供了一些新的对话框,并且比以前的版本更方便使用。它还提供了一些旧版本报表设计中所没有的新特色。你可以通过改变一个名为“_REPORTBUILDER”的系统属性来决定使用哪种报表设计器,如下所示:   *--如果要使用新版本的报表设计器 _REPORTBUILDER = HOME() + 'ReportBuilder.app'   *--如果要使用旧版本的报表设计器_REPORTBUILDER = ''   报表输出引擎:与报表设计器一样,你可以控制是否选用新版本的报表输出引擎。但与报表设计器不同的是Visual FoxPro 9默认报表输出引擎为旧版本方式。主要是因为在新版本的输出引擎中使用了GDI+库,而老版本的输出引擎使用的是GDI库,使用老版本的输出引擎就可以让应用程序可以在不用版本的windows上显示出同样的输出效果。你可以用如下命令来切换你的输出引擎:   *--使用新版本的输出引擎 SET REPORTBEHAVIOR 90   *--使用旧版本的输出引擎 SET REPORTBEHAVIOR 80      在下文中我们假定使用的是新版本的报表设计器和输出引擎。   数据环境(DE)   Visual FoxPro 9的报表设计器能与让多个报表共享同一个数据环境。数据环境能够以类的方式保存,并在需要的时候被报表载入。这为那些需要制定通用报表数据环境的应用程序提供了方便。   要将数据环境保存为一个类,首先你要为报表定义一个数据环境,然后激活数据环境窗口,并在“File”主菜单中单击“Save As Class...”选项。   这样系统会弹出一个新的对话框(参见图1)。在这种情况下,Save单选按钮组中只有“DataEnvironment”处于允许状态。 图1. 使用“Save As Class”对话框指定要保存的类名以及所在的类库,并将指定报表的数据环境保存在这个类中。 Visual FoxPro 9.0 的报表设计器(2)  载入数据环境   除了能为报表定义数据环境以外,Visual FoxPro 9还能让你将某个报表的数据环境类载入到报表中去。“Report”菜单中的“Load Data Environment...”选项可以让你选择到底载入哪个数据环境。   通过报表设计器载入数据环境   如果要为一个新报表载入数据环境,那源数据环境的所有代码和成员变量都会复制到新报表中。这表明当你改变原来报表的数据环境后,并不会对新报表的数据环境产生任何影响。   图2显示了当你从“Report”主菜单中单击了“Load Data Environment...”选项后弹出的属性对话框。你可以在里面选择从哪个报表中复制源数据环境。 图2. 单击”Data Environment“选项卡,从中选择你要从哪个报表中复制数据环境 在上图中,单击“Copy from another report file”单选按钮,然后单击“Select...”按钮,这样会弹出一个打开对话框,你可以从中选择从哪个报表中复制。如果你选中了一个报表,那系统弹出一个确认框。   假如我们要将某个报表的数据环境复制到当前报表中去,Visual FoxPro 9会警告你将覆盖当前报表的数据环境,你必须选择“是”才能继续进行复制。这个提示功能可以防止由于你的误操作而将当前报表的数据环境覆盖掉。如果你选择“否”的话,那复制就会取消,如果选择的是“是”,那就会真正进行复制操作,并且当操作完成后,系统会出现另一个对话框,提示你操作完成。   现在数据环境已经复制成功了,你可以操控新的数据环境。但你要始终记得原报表数据环境的改变并不会对新的数据环境有任何影响。   从一个类中载入数据环境   当要从一个类中载入数据环境时,你必须要为新报表的数据环境写一些额外的代码,使得它能够动态地绑定源数据环境,并且初始化它的一个实例。这意味着如果从类中载入数据环境时,对源数据环境做的所有改动会影响到所有使用它的报表。   你同样可以用图2所示的报表属性对话框来完成这个效果,先单击“Link to a visual DE class”单选按钮,然后从系统弹出的打开对话框中选择你要载入的类库以及类名,当你点击确定按钮后,当前报表的数据环境将得到更新,并且系统会给出相应提示信息。   其实Visual FoxPro自动为数据环境的如下5个方法中加入了一些代码:Init()、BeforeOpentables()、AfterCloseTables()、Destroy()和Error()。有些方法中加入的代码非常简单,仅仅是一个DODEFAULT()命令,这个命令不执行任何操作。其原因是BindEvents()方法必须保证数据环境的这5个方法中的代码行数超过一行才能执行。你可以手动查看这些自动生成的代码,但我强烈建议你别去动这些代码。 Visual FoxPro 9.0 的报表设计器(3)   保护   如果要在Visual FoxPro 9使用报表设计器或者标签设计器,你可以为一个或多个的布局对象设置保护。这种特性可以让你的用户只能对报表进行有限的修改。   你可以为布局对象设置5种保护模式,域对象有着另外的保护选项。带区(Band)有两种保护模式可供你选择。并且你也可对报表本身设置不同的保护方式。   保护一个对象   要在报表设计器中为一个布局对象设置保护,通过激活此对象的属性对话框即可操作,你可通过用右键点击此对象,并在弹出的快捷菜单中选择相应的菜单项,或者直接双击此对象。图3显示了一个布局对象属性对话框的保护页,你可以为布局对象设置如下5种保护方式:  • 对象不能被移动或改变大小。它使得用户不能在设计器中移动此布局对象,并且用户不能改变此对象的大小。   • 对象不能被修改。它使得用户不能修改此布局对象的属性。   • 对象不能被删除。 它使得用户不能删除此对象。   • 对象不能被选中。用户不可以选择此对象,当对象处于这种保护方式下时,用户不能移动它或改变它的大小,同样也不能修改或删除它。   • 对象在设计器中不可见。它使得此对象在报表设计中不可见,当对象处于这种保护方式下时,用户不能移动它或改变它的大小,同样也不能修改或删除它。 图3. 布局对象属性对话框中的保护页   这个对话框还有一个名为“Design-time caption”的输入项,它只对域对象有效。你可以在其中输入域对象的名称,这样在报表设计器中就不会显示域的表达示名称,而是显示你输入的名称。当域的表达式非常冗长时,这种显示方式可以使得报表设计器的用户界面更加友好。   保护一个带区(Band)   在报表设计器中要保护一个带区的话,请先激活此带区的属性对话框。你可以通过选择“Report”菜单中的“Edit Bands...”菜单项来打开这个属性对话框,也可以直接双击带区的灰色条。图4显示了一个带区属性对话框的保护页,你可以选择以下两种保护模式:   • 带区不可修改。这可以防止用户修改带区的属性。   • 带区不可改变大小。这可以防止用户改变带区的大小。 图4. 带区属性对话框中的保护页   保护报表本身   要为一个报表设定保护方式,要先激活此报表的属性对话框。你可以通过选择“Report”菜单中的“Properties”菜单项来打开这个属性框,也可以右键单击此报表来弹出这样一个菜单。图5显示了一个报表属性对话框的保护页 图5. 报表属性对话框中的保护页   这个对话框的上半部分可让你禁止用户使用属性对话框中的某些属性页。当你选择了相应的检查框后,报表属性对话框中的某些属性页会变得不可用。但“Protection”检查框总是保持选择状态而且不允许你对它进行改动。另外由于“Ruler/Grid”属性页无法保护,因此“Ruler/Grid”检查框也总处于禁止状态,这两个检查框之所以显示是为了保持属性页与检查框的一致性。   属性对话框的下半部分可以禁止用户使用某些菜单项。当你选择了相应的检查框后,相应的菜单会变得不可用。   在命令中设置保护标志   如果要通过命令方式来调用报表设计器或标签设计器中的保护方式,则应该使用PROTECTED关键字,如下所示: CREATE REPORT MyReport PROTECTED MODIFY REPORT MyReport PROTECTED CREATE LABEL MyLabel PROTECTED MODIFY LABEL MyLabel PROTECTED   如果没有指定PROTECTED关键字,报表设计器不会为任何布局对象加上保护 Visual FoxPro 9.0 的报表设计器(4)  增强的用户界面(UI)   报表设计的用户界面作了很多改进,使得用户能更方便、更直观地设计出报表。菜单项也作了很大的调整,上下文菜单做了改进,报表设计器的工具栏中增加了一些新选项。表达式构造对话框(Expression Builder )和表达式构造选项对话框(Express Builder Options)都有了新的特性,此外报表设计器对其它一些用户界面作了细微的改进。   菜单   菜单加入了一些新项目,一些原有的菜单项被更名使得它们表达的意思更清晰,此外一些常用的菜单项被重复以便用户更方便地访问到它,具体如下:   • “File”菜单下增加了“Save As Class...”菜单项。   • 报表设计器的工具栏加入了一个名为“水平线(horizontal lines)”的控件,它用来把Grid Lines和Show Position这两个控件与其它的控件分割开来。   • “Report”菜单增加了“重贴标签(relabled)”、“新建(new)”以及“打印预览(Print Preview)”等菜单项。   快捷菜单   对现有的快捷菜单添加了一些新条目,使得菜单条目与相应的对话框能保持更好一致性。  • “全局(Global)”快捷菜单增加了一个名为“重贴标签(relabled)”的菜单项。   • 通过右键单击任意带区(band)的灰色栏可以弹出名为“带区(Band)”的快捷菜单。   • 通过右键单击任意布局对象可以弹出名为“布局对象(Layout Object)”的快捷菜单。   工具栏   如图6所示,报表设计器的工具栏增加了两个新的控件:页面设置控件和字体控件: 图6. 报表设计器增加的两个新控件   表达式构造对话框   表达式构造对话框为“表达式域(Expression for Field)”输入框提供了一个更大的输入空间,允许用户输入更多的报表表达式。   如果你将_REPORTBUILDER这个系统属性设为空的话,那表达式构造对话框会指定本地行为,只有在数据环境中定义的数据表才能够显示在对话框中的列表中。那些没有在数据环境中定义的数据表则不会在列表中显示。   如果将_REPORTBUILDER系统属性为ReportBuilder.app的话,那表达式构造对话框则会呈现另外一种行为。首先,_GETEXPR中定义的表达式构造器会取代本地的表达式构造器。   表达式构造对话框还有一个下拉组合列表框,你可以从中选择要操作的数据表。只有当前已使用的数据表才会出现在这个列表框中。需要强调的是报表设计器是不会自动打开数据环境中定义的数据表的,因此如果数据表没有被打开的话,那它便不会出现在这个列表框中。   采用这种设计方式事,当用户使用你定义的报表时,你可以很好地控制用户能访问哪些数据表,不能访问哪些数据表。你可能在数据环境中定义了若干个数据表,但你不希望用户能访问所有的数据表,这时你可以打开一些允许用户访问的数据表,而其他未打开的数据表则对用户不可见。   鼠标指针的改进   当报表中对象处于大小可变状态时,鼠标指针会发生相应的改变(参见图7)。 图7. 当一个对象处于大小可变状态时,鼠标指针发生的改变。   多项选择对话框   VIsual FoxPro 9提供了一个多项选择对话框,你可以通过它一次性地设置多个布局对象的Portection和Print属性。它也允许你对单个布局对象的其它属性进行修改。要使用这种功能,先要选定多个布局对象,然后在任意一个对象上面单击右键来弹出这个对话框,如图8所示: 图8. 多项选择对话框   所有被选定的对象出现在对话框的“Selection”属性页的列表中。如果你要选取报表中所有的布局对象的话,用CTRL+A组合键可以对它们进行全选,然后再单击右键即可。   图8中的“Sort by”选项组允许你将布局对象按类型或出现在报表中的位置排序。“Remove from list”按钮可以删除列表中的布局对象。如果你双击列表中的某个对象,那么“Properties”属性页就会激活,并且显示出这些布局对象在报表中的某些属性。如图9所示,你可以一次性更改所有出现在“Selection”属性页中的布局对象的某些属性。 图9. 通过“Properties”属性页来修改布局对象的保护属性以及打印属性。   如果你选中了“Apply these protection settings to the selected objects”检查框,那可以对列表中的布局对象设置保护方式。如果你选中了“Apply this condition to the selected objects upon saving”检查框,那么可以允许打印。你可以根据自已的需要对保护及打印做更进一步的设置,设置完毕后,单击“OK”按钮便可同时改变所选的布局对象的这些属性。   更大的缩放级别   预览窗口有了更大的缩放级别,可以从10%缩放到500%。   布局对象的增强   布局对象也做了一些改进,包含一个操控模板字符的可选项,字符表达式的裁剪模式,以及能指定布局对象的相对位置和绝对位置。   模板字符   域属性对话框增加了对模板字符的一些新支持,它们分别是覆盖(Overlay)和交错(Interleave)。用来支持字符的一些特殊格式。 当你使用覆盖方式时,特殊字符会被当做数据的一部分,并且会覆盖其它的字符。举个例子来说,当你使用一个格式化字符串“999-999”时,而用户实际输入的数据是“123456”,那报表的最终结果将会显示为“123-56”,注意数字“4”被格式化字符中的“-”覆盖了。 当你使用交错方式时,特殊字符会插入到当前数据中。举个例子来说,当你使用一个格式化字符串“999-999”时,而用户实际输入的数据是“123456”,那报表的最终结果将会显示为“123-456”,注意“-”插入到了数字“3”和数字“4”的中间。 Visual FoxPro 9.0 的报表设计器(5)  字符表达式的裁剪模式   在Visual FoxPro 9以前,当域对象中的文本过长时一般都会被自动裁剪。在Visual FoxPro 9中,你可以指定域对象的如下几种裁剪方式:   • 缺省裁剪方式。这种方式类似于以前版本的Visual FoxPro的处理方式。   • 裁剪最近的字符。它将多余的字符全部裁掉,直至刚好满足输入域的长度。   • 裁剪最近的单词。它将多余的单词全部裁掉,直至刚好满足输入域的长度。   • 文件裁剪方式。如果输入域中的内容中一个非常长的文件路径,那么中间的路径将会以省略号代替,只保留头尾路径。   大小及位置   现在可以更方便地控制布局对象的大小以及所处的位置。与原版本不同,当在新版本报表中加入一个对象时,此对象的“From page top”、“From left”、“Height”和“Width”属性都会自动设置。在报表设计器中,“From page top”属性是指对象相对于页面顶端的相对位置。此对象上的所有灰色栏的高度也都被计算在内。改变对象的“From page top”属性有可能将对象移到到另一个区带中去。 相对位置:“From page top”属性和“Height”属性共同确定了当前对象是处于绝对位置还是相对位置。当对象的“From page top”属性的取值范围在报表的区域内,并且“Height”属性小于或等于所在区带的高度时对象就处于相对位置。一般来说,只有区带内的对象才需要使用相对位置,而像Page Header和Page Footer这样的对象则不需要使用相对位置。   绝对位置:当对象的“From page top”属性的取值范围在报表的区域以外,并且“Height”属性大于所在区带的高度时对象就处于绝对位置。绝对位置意味着对象在每页报表中都有着一个精确的位置,不会发生偏移。   我们可以利用绝对位置来为报表增加水印效果。将一幅水印图像放在Page Header区带中,并且将它设为“缩放内容,保持形状”模式。将水印图像的“From page top”属性和“From left”属性设为某组值,这组值相当于你的水印图像在报表中的左上角坐标。然后通过改变“Height”和“Width”属性来指出水印的大小,但注意的是不要将图像的尺寸设得过大,以免超过了打印区域的边界。 增强的数据分组功能   Visual FoxPro 9报表设计器对数据分组功能做了一些改进,增大了数据分组的最大数量限制以及对水平栏的改进。   最大数据分组数   最大数据分组数从原来的20个增加到了现在的74个。实际原来的版本也支持最大74个数据分组数,但由于原来的界面只支持20个数据分组的输入,从而导致了这种限制。   水平分栏   在以前的版本中,如果要为一个数据组定义多个水平分栏的话将会浪费很多报表空间。并且第一行第一列与报表的顶端之间有一些空白,数据还会从行1列2开始显示。并且在每个数据组中都会有一个多余的区带,如图10所示。即使数据组的页头区带的高度为0,Visual FoxPro 仍然将保留这些空白,如图11所示。 图10. 当为数据组定义多个水平分栏时,原来版本的Visual FoxPro浪费的报表空间 图11. 即使数据组的页头区带高度为0,原来版本的Visual FoxPro仍将保留这些空白   在Visual FoxPro 9中数据组分栏得到了改进。当报表设计器发现一个新数据组时,它将从第一列开始显示,直至満行。如果不满一行,那剩下的部分将以空格填充。如果还有未打印完的明细记录的话,那这些记录会从下一行开始输出,如图12所示。如果数据组的页头区带的高度为0,则Visual FoxPro 将不会保留任何空白,如图13所示。 图12. 将数据组进行水平分栏时,Visual Foxpro 9将节省更多的空间 图13. 如果数据组的页头区带高度为0时,Visual Foxpro 9不会保留任何空白 Visual FoxPro 9.0 的报表设计器(6)  多条明细区带   这个新增功能其实早在以前就很需要,它是对老版本的一个很大的改进。它能让你在一个父表中为每一条记录处理相对应的子表,这种报表格式的一个典型实例如图14所示。 图14. 这个报表为每个客户报告了其购买保险的详细信息   数据表与关联   要想熟练地使用这一新特色,你必须明白父表是怎样与子表一起协同工作的。我们以图15所展示的报表为例,它使用的数据库环境如下:   • 客户表(Customer)是父表,它包含了所有购买保险的客户。   • 家庭成员表(Members)是客户表的子表,它包含了客户的所有家庭成员。   • 交通险表(Vehicles)也是客户表的一个子表,它包含了客户所购买的交通险。   • 家庭险表(Homes)也是客户表的一个子表,它包含了客户所购买的家庭险。   主表   必须有一个表来做为报表的主表,在本例中,客户表正是这样的一个数据表。如果你用数据环境来定义数据表的话,那必须将它的InitialSelectedAlias属性定义为这个数据表。 如果你用代码的方式来定义数据表的话,那要保证当此报表运行时,客户表必须处于当前打开的工作区。   目标别名(Target Alias)   所谓目标别名,是指在报表某个特定的区带中做为主表的那个数据表。在本例中,家庭成员表是明细区带1的主表,交通险表是明细区带2的主表,而家庭险表则是明细区带3的主表。   如果没有为某个明细区带定义主表,那它就会呈现出与老版本的Visual FoxPro一样的行为(每个父表只会处理一个明细区带)。但如果你定义每个父表都为主表的话,那结果会全然不同。Visual FoxPro 将依次处理父表中所有记录,并在每个明细区带中依次将它们输出。    关联   关联在如何控制多条明细区带的输出中起到的重要的角色。Visual FoxPro通过父表与子表之间的关联来进行记录的导向。你可以通过SET RELATION或者SET SKIP来定义这些关联。如果你在数据环境中打开了这些数据表,并且数据库中已经定义了它们之间的关联关系的话,那父表与子表之间将会自动生成关联。   如果你采用代码方式打开数据表的话,那列表1将告诉你如何将图15所示的数据环境建立起来。   列表1.建立图15的数据环境   如下代码展示了如何将父表与子表建立报表关联的例子。   *--打开子表 USE Members IN 0 ORDER CustomerFK USE Vehicles IN 0 ORDER CustomerFK USE Homes IN 0 ORDER CustomerFK   *--打开父表 SELECT 0 USE customer ORDER CustomerPK   *--为父表和子表建立报表关联 SET RELATION TO CustomerPK INTO Members SET RELATION TO CustomerPK INTO Vehicles ADDITIVE SET RELATION TO CustomerPK INTO Homes ADDITIVE 定义多条明细区带   如果你新建一个报表,那它缺省地为这个报表只设置一个明细区带。通过可选区带对话框可增加额外的明细区带。从“Report”菜单中选择“Optional Bands...”菜单项就可以打开这个对话框。它其实就是原来版本中的“标题/小结(Title/Summary)”对话框。   单击“Add”按钮便可以增加一个新的明细区带,你可以为一个报表定义最多20个明细区带。   定义主表   通过明细对话框你可以为每一个明细区带定义其主表。从“Report”菜单中选择“Edit Bands...”就可以打开这个对话框,或者双击明细区带的灰色栏也可达到同样效果。   主表实际上是一个表达式,你必须将相应的数据表名用引号引起来。如果你定义了某个主表,则相应的明细区带的灰色栏将显示它的名字。   如果你要建立一个多条明细区带形式的报表,请别忘了在字段前面加入数据表名的前缀,格式为“数据表名.字段名”,这样可以防止不同的数据表之间的同名字段冲突。   页头和页尾   多条明细区带的另一个改进就是能为每一个明细区带增加独立的页头和页尾。这与页头和页尾分组有些类似,但仍然有一些不同。当每个父表记录的处理流程如下:   • 明细区带1的页头被处理。   • 处理明细区带1中主表相应的所有子表记录。   • 明细区带1的页尾被处理。   • 明细区带2的页头被处理。   • 处理明细区带2中主表相应的所有子表记录。   • 明细区带2的页尾被处理。   • 明细区带3的页头被处理。   • 处理明细区带3中主表相应的所有子表记录。   • 明细区带3的页尾被处理。   • 以此类推......   要为每一个明细区带增加独立的页头和页尾,你必须在详细信息对话框中的“Detail Header/Footer”的检查框中选中相应的明细区带。也可通过点击明细区带上方的标题栏来对明细区带进行排序。   学海无涯   Visual FoxPro 9报表设计器增加了如此多的特色来帮你建立更好的报表,以至于你不得不花更多的精力来学习它的新功能,但我认为这些学习是非常值得的。新的数据环境允许你在不同的报表中共享它。对报表的各个部分提供了强有力的保护方式,用户界面也做了一番改头换面,让你得到更舒适的开发体验。布局对象与数据分组的增强给你提供了更多的报表操控能力。最后,新推出的多条明细区带的特色能挖掘出报表设计器的更多功能。所有的这些改进可让你创建出非常复杂、功能非常强大的报表。   三思同志 发表于2005-04-23 7:57 AM  IP: 218.15.22.* 介绍 CursorAdapter 类 vfp8最激动人心的变化是 CursorAdapter 类,它为不同的数据源提供通用的数据接口。 下面介绍怎样用 CursorAdapter 改变在 VFP 8 中连接数据的方式,包括,native tables, ODBC, OLE DB, XML. 。 CursorAdapter 类是 VFP 8 开发组的最给人印象深刻的成就之一。 它将会改变许多开发者连接各种不同的数据来源的方式。 开发小组在 VFP 存取数据的方式方面作了重要改变,将本地和远程数据连接方式进行了统一。另外,创建 CursorAdapter 类对那些已经熟练使用视图和SPT人来说并不费力。对使用ADO RecordSets 或XML可扩展标示语言文件的人也不费力。 CursorAdapter 类的独特之处在于,它是第一个提供本地游标、ODBC、ADO、XML数据源连接的基类,在一个类里面实现了所有连接功能。换句话说,要将ODBC数据源,ADO RecordSet 或XML可扩展标示语言文件翻译成一个 VFP 游标,CursorAdapter 类完全可以胜任。 你或许会说 CursorAdapter 是较早版本中本地视图和远程视图技术的替代 (注意: VFP 8中仍然保留这些功能). 但是在一些情形中,它也代替了SPT, 以及减少了使用ADO和XML时的代码量,可以直接使用ADO和XML。 CursorAdapter 的最大好处是,需要在同一个程序内连接到多个数据源的时候,它为你提供方便。举例说,如果你的程序大部分数据来自 SQL server,但是同时需要与XML可扩展标示语言连接,CursorAdapter 可以整合这两种情况,使程序取回的数据作为VFP的游标。 另外的一个例子是数据现在被储存在 VFP 表中 , 但是计划要移到一个数据库服务器 , 比如 SQL server或Oracle。 你需要先建立一组 VFP CursorAdapter 类,必要的时候以 SQL server以外的数据库代替这些类。 但是,就象我们能跑之前必须学会走一样,先概览一下 CursorAdapter 类和它的特性。 然后,使用 CursorAdapter 类来设计数据类是比较容易的。 建立第一个 CursorAdapter 类,象其他类一样,学习怎样使用它的最好办法是了解建立过程。第一次建立这些类的时候复杂程度低一些,我们开始用 CursorAdapter 类存取 VFP 本地的数据。 这很象使用本地视图取回 VFP 本地表的数据。 稍后在这一篇文章中,我们将会使用另一个 CursorAdapter 类连接到 SQL server数据库,ODBC和XML。 首先,你有二个方法建立 CursorAdapter 。 你能使用数据环境建立,也可以手动通过程序或类设计器来创建一个经过一个CursorAdapter类 。 这一个例子将会使用数据环境建立;较迟的例子将会手动建立. 如果你不熟悉 VFP 8 变化后的数据环境, 你可能认为在设计环境下创建的 CursorAdapter 只能在表单中使用,不能用于类。 然而, VFP 8 中已经改善了设计环境,因此不需要在表单中就可以创建。 用creat class命令创建一个新的数据环境类。 确定从下拉列表中选择based on数据环境类(dataenvironment) 。类名为Tests,所属类库名为tests.vcx. 创建的类在类设计器中出现后,右键单击Data Environment ,选择builder,起动数据环境建立向导。 在数据源类型项下,注意可选的选项。 因为第一个例子将会连接到本地的 VFP 表,选择native。 选择完以后, 使用‘省略号’按钮选择 Northwind 数据库(我这里是gzdata数据库)。 (默认位置是 c:/ program files/microsoft visual foxpro 8/ sample/northwind/ northwind.dbc) 下一步,点cursors 页, 它初始值是空的。 在列表框中,选择new按钮用 CursorAdapter 设计器创建一个新的 CursorAdapter 类。 首先,你应该看看Properties页,这里提供选项来选择类的名字和由类产生的游标的别名。 确定提供一个不同于表名字的别名,避免产生混乱。 在这里,使用 caCustomer 做类名, cCustomer 作为别名。 如果想让这个类用和数据环境一样的数据源,应该选择 "Use DataEnvironment data source" 。 注意你可以为 CursorAdapter 设置不同的数据源, 允许你在不同的类之间整合数据源.( 例如一个类使用ODBC数据源,另一个类使用XML数据源) 要定义CursorAdapter 如何返回来自数据源的数据,使用设计器中的Data Access 页。 按build按钮激活一个对话框,可以选择游标包含的字段。 在这个例子中,选择Customers表, 然后选择Customers.*。 点击向右的箭头移动选择项, 然后点ok。 这为你建立下列SQL语句: select CUSTOMERS.* from CUSTOMERS 如果你想添加过滤器,连接, 或其他条件到查询,你可以在列表框中直接键入。 如果你想建立带参数的查询,有一些选项,在本文后面介绍。 现在, 让我们添加WHERE子句: select CUSTOMERS.* from CUSTOMERS where companyname like 'C%' 这里可以看出源表和游标的不同,因为只有少数记录符合WHERE子句。 在第二个编辑框(schema)中已经为为你创建了字段列表。通常在继续以前花几分钟看看字段顺序是否符合你的习惯是有好处的 在这一页的下半部分有数据包设置对话框(data fetching),用来设置怎样处理远程数据包,当用vfp作为数据源的时候,这里的设置不发挥作用。(如上图)这里我们保留默认设置,稍后再讲述具体细节。在本页的底部附近是缓冲模式设定, 允许你设置任何被关联的表单的缓冲模式。有两个选项:开放式行缓冲和开放式表缓冲。 通常,你使用开放的表缓冲模式,除非你有特殊要求使用行缓冲模式。在这个例子中设置为开放式的表缓冲。最后,“break on error”控制CursorAdapter类怎样来处理错误。默认设置是类(class)自行捕获错误,并且允许你用aerror()函数捕获这些错误。选定这个设置,CursorAdapter类内部不管发生什么错误,vfp都会出现错误信息。也就是说,你需要使用ON ERROR命令或者‘类’的ERROR事件来排除不需要报错的情况。通常情况下不选这项设置,以便程序能处理任何发生的例外情况。 最后一页 (auto update) 配置如何更新源表。在通常情况下,选择“自动更新(auto-update)”和“更新所有字段(update all fields)”。 这将使cursoradaper类对游标(cursor)中数据的任何改变自动建立适当的更新、插入、删除机制。然而,你必须选择游标中的主关键字段,以便这些机制(更新、删除、插入)唯一的识别源表中的记录。在本例中,CustomerID是关键字段。因此,需要在其前面大上‘对号’。其他的设置暂时保留默认值。具体设置办法在本文后面讲述。设置完cursoradaper后,点击“ok”按钮,回到数据环境设置。此时,你应该在左边列表框中能看到caCustomer类, 在右边看到细节。如果你想更改这个类,你可以随时用数据环境‘builder’更改,选择需要更改的CursorAdapter 类,然后点击builder按钮。 存取 VFP 数据 此时,你可以测试数据环境,看看是否能取回在 CursorAdapter 的指令中筛选的数据。 使用命令窗户,例示 DE 类而且唤起 OpenTables 方法: lo = NewObject("deTest","Tests.vcx") ? lo.OpenTables() BROWSE 一个特殊情况是,CursorAdapter的游标连接到其它对象,如果你毁坏了指向CursorAdapter类的对象,会丢失游标和其中的记录。这就是说,你必须确保CursorAdapter对象参数在你打算存取的关联游标的范围内 编辑 VFP 数据 现在, 让我们看看是否能够更新游标,并且把更新准确地发送到源表。在命令窗口测试以下命令: REPLACE contactname WITH 'My Name Here' ?TABLEUPDATE() SELECT customers BROWSE 浏览customers别名,你会发现修改过的记录已经更新到源表中。 如果你在发送replace命令之前没有移动记录指针,客户ID中 'CACTU' 所在的记录被修改。 不管你修改哪条记录, 这证明 CursorAdapter 是能够被更新,而且更新能够准确地被发送到源表。 让我们打开你刚刚测试的数据环境类,这不只是一个练习—它是一个很棒的方法学习当你决定在数据环境以外建立自己的类的时候,该如何正确地配置一个 CursorAdapter 类。 虽然数据环境有一些属性改变和一个方法, 我们实际上对那些改变不感兴趣。看一下下拉列表框中的属性列表,选择 caCustomer 类,看看建立 CursorAdapter 类的时候需要进行的设置。 表 1 概述被builder和每个 PEM 所做改变。 所有的属性包含在 "see Init" ,通过INIT方法中的代码来设置这些属性。 那一段代码显示在list 1中。 在builder设置完以后,看看一些属性是怎样设置的,这是最好的学习方法。你可以在这里或者通过builder改变设置值。然而,如果在这里改变设置值,你需要冒破坏一些功能的风险,因为你在这里改变的属性有可能在builder里不会发生改变。 不管怎样,你能在 Init() 代码中看到 SelectCmd 属性是如何叙述的, 同样在 KeyFieldList , UpdatableFieldList 和 UpdateNameList中也可以看到。 特别注意 UpdateNameList 属性—这个属性列出游标的每一个字段以及源表中对应的字段(带表名称)。 当从头创建你自己的 CursorAdapter 类的时候,你可能想在这个列表中省略表名字。 然而,如果不使用精确的格式,你的更新将会失败, 但是没有错误提示。 在后面讲不通过builder建立一个类的时候我将再说这一点。 前面我说过 CursorAdapter使用本地的数据源的时候 , 本质上是一个替代了本地视图。如果你曾经用过本地视图,你可以发现类似的地方: 生成一个SQL select语句,定义哪些字段将被更新,设置关键字段,剩下的事情让 VFP 来做。 一旦游标中取回数据,可以使用 TableUpdate() 发送更新到源表,而且 VFP 自动地建立必要的update,insert,delete语句。 还是以上面的例子, 取消cCustomer 别名中contact字段被替换的数据。通过TableUpdate , VFP 自动地产生 (并提交)下列更新命令尝试更新: UPDATE customers ; SET CONTACTNAME=ccustomer.contactname ; WHERE ; CUSTOMERID=OLDVAL('customerid','ccustomer'); AND ; CONTACTNAME=OLDVAL('contactname','ccustomer') VFP 能够根据CursorAdapter的KeyFieldList属性和部分UpdateNameList属性中的值产生where子句。它通过子句记录那些已经被改变或添加的记录,保证你不会更新那些别人已经更新过的记录。注意,这是因为我们在它的WhereType 属性中保留默认值 "key fields and any modified fields." 错误处理 明显的,当用 CursorAdapter 更新数据时候 ,并不能完全如你所愿。你知道,多种原因可以导致TableUpdate 更新失败, 例如更新冲突或记录加锁失败。你必须对 CursorAdapter 类做特别处理来发现这些问题吗? 答案是,"视情况而定 ." 我们来建立一个简单的更新问题:给CursorAdapter尝试更新的一条记录加锁。如果类设计器仍然是开着的,关掉它。然后, 像前面做过的一样,用 NewObject 函数启动detest类,而且启动 OpenTables 方法。浏览游标以便你能看到数据,但是先不要改变任何东西。 现在打开 VFP 8 的第二个实例,这样就能够加锁记录。 在命令窗口中运行下列语句加锁你将尝试更新的记录: OPEN DATABASE (HOME(2)+"Northwind/northwind.dbc") USE customers LOCATE FOR customerid = 'CACTU' ?RLOCK() 你应该能返回.T.,表明记录确实被 VFP 的这一个实例加锁。 回到 VFP 的第一个实例,在命令窗口中运行下面的代码: REPLACE contactname WITH 'updated' SET REPROCESS TO 2 SECONDS ?TABLEUPDATE() 在这种情况, TableUpdate 返回.F.,表现记录锁阻止更新成功。 如果你用 AERROR() 捕获错误而且显示错误结果, 你会看到错误信息 "记录没加锁"。 这意味着如果你对设置缓冲的表直接操作而不是一个游标,你能用同样的办法处理错误。 不幸地,不是所有的预期错误都会这样表现出来。需要特别注意的是更新冲突,比如一个使用者企图覆盖其他人所作的修改。想看看这种行为的结果,在当前的 VFP 实例中(CursorAdapter 正在使用)运行下面的代码: ?TABLEREVERT(.T.) REPLACE contactname WITH 'client 1' 现在转变在到第二个例证并且发行下列指令: CLOSE DATABASES all OPEN DATABASE (HOME(2) + "Northwind/northwind.dbc") USE customers LOCATE FOR customerid = 'CACTU' REPLACE contactname WITH 'client 2' BROWSE 返回第一个实例, 尝试用 TableUpdate 发送更新: ?TABLEUPDATE() 在这种情况, TableUpdate 错误地返回.T.,使你相信更新成功了! 然而, 事实上并没有成功,而且这可以被CursorAdapter的CursorRefresh() 方法证明, 用下列代码: ?TABLEREVERT(.T.) ?lo.caCustomer.CursorRefresh() CursorRefresh 方法告诉 CursorAdapter 再运行 SelectCmd 里的语句,并且取回来自源表的最新数据。 对ContactName 字段的测试说明 CursorAdapter 根本没更新字段值! 解决这一个问题的最简单的方法要利用 CursorAdapter 的 AfterUpdate 方法。这一个方法在 TableUpdate 发送保存每条记录的请求后被执行。注意这个‘方法’只对当前记录有效。 如果记录是新的,或者记录已经被删除,会激活AfterInsert 或 AfterDelete 方法。 AfterUpdate 方法捕获一些参数, 包括最初的字段状态,是否被强制更改, 和作为更新的命令。 最后一个参数 , lResult,是我们这部分主题最重要的,因为它告诉我们更新过程是否成功。 使用的另一个重要角色解决更新冲突问题的是系统变量 _tally, 它告诉我们最后一次操作影响了多少记录。 因此,如果 lResult 放回成功的, 但是 _tally是零,那么说明没有记录被更新,那么你可以判定这种情况是一个更新冲突。 简单说,解决这一个问题的简单方法是把下列代码加入 CursorAdapter 类的 AfterUpdate 方法: LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd, lResult IF lResult AND _TALLY = 0 THEN ERROR 1585 && update conflict ENDIF 有趣的是你在屏幕上看不到错误信息;错误信息被 TableUpdate “限制住了”,这就迫使你使用 AError 函数分析更新失败的原因。出现这样的现象是因为 BreakOnError 属性保留了默认值 ,也就是错误不引起程序中断。如果你把这个属性设为“ture",那么"更新冲突" 错误信息就会出现,如果作了定义,你的ON ERROR句柄会被运行。 这一个更新冲突问题是为 CursorAdapter设计的,因为 VFP 8 没有自动发现这个问题方法。因此,当不用本地数据源的时候,这个代码( 或相似的代码)会用在你的CursorAdapter类设计中。 CursorAdapter with ODBC 现在你已经有了一定的基础, 让我们继续看看当后台数据库用 SQL server的时候怎样进行设置。我们将从使用 VFP 通过ODBC连接 SQL server上的 Northwind 数据库开始。同样, 让我们从头建立 CursorAdapter 以便可以看见类需要设置的各个方面。 首先,用下列指令在类设计器中建立一个新类: CREATE CLASS caODBC OF tests as CursorAdapter 这里需要设置的最重要的属性是 DataSourceType 属性。 因为我们想通过ODBC连接 SQL server,将这一属性设置为ODBC。当使用这种连接方式的时候 , DataSource 属性需要有一个确切的连接句柄,可以用 SQLConnect 或 SQLConnectString 函数建立。 在任一情况,这些功能应该在 CursorAdapter 类的 Init 方法中使用下列代码: LOCAL lcConnStr, lnConn ** string assumes trusted connection (integrated security) lcConnStr = "Driver=SQL Server;Server=(local);DATABASE=Northwind" lnConn = SQLSTRINGCONNECT(lcConnStr) IF lnConn > 0 THEN THIS.DATASOURCE = lnConn ELSE ** unable to connect ENDIF 连接字符串假定你使用可信赖的关联到 SQL server;如果你使用安全的连接到 SQL server,把 "uid"= 和 "pwd"= 串加入连接句柄指定使用者名称和密码。 任何一种情况下,返回的是连接柄 , 或错误发生时的否定值。 这一个连接柄被指定为 DataSource 属性以便 CursorAdapter 知道该到哪里声明。 下一个步骤是建立 SelectCmd 以便 CursorAdapter 知道需要从数据源取回什么数据。还有一个最好的放置地方是 Init 方法, 因为属性表限制字符串的长度。 把下列代码加入 Init 方法中,设定 DataSource 属性代码之后, 取回公司名字中以 "C" 开头的客户列表: This.SelectCmd = "SELECT " + ; "CustomerID, CompanyName, ContactName, " + ; "Address, City, Region, Country " + ; "FROM Customers WHERE CompanyName LIKE 'C%'" 然后,你应该告诉 CursorAdapter 通过调用CursorFill 方法填充被关联的游标。 你可以省略这一个调用,并且手动从类之外调用, 或放在 Init 方法中,以便它能够自动填充游标。在Init 方法中设置 SelectCmd 之后,可以用 This.CursorFill()调用。 最后,你应该在类的Destroy方法中加上一些代码,以便在对象从内存中释放以后减少到服务器的连接。 没有这些代码的话,每一个新的实例将会产生一个到服务器的新连接, 并且不会释放它: IF this.DataSource > 0 THEN SQLDISCONNECT(this.DataSource) ENDIF 藉由这些变化,你拥有一个实用的 CursorAdapter 类,它能够生成一个只读的游标。在允许更新之前,测试一下类,确保可用,并能准确取回数据。用下列代码测试: lo = NEWOBJECT("caODBC","tests") BROWSE 注意,你不需要像数据环境那样调用 OpenTables 方法。这是因为你把 CursorFill 方法直接加入了 Init 方法,使类在实例化之后自动地填充游标。 Updating ODBC Data(更新ODBC数据) 为了使这个类可更新,你必须正确地给Tables, KeyFieldList , UpdatableFieldList 和 UpdateNameList 属性赋值。 并且设定 AllowInsert , AllowUpdate 和 AllowDelete 属性为true, 确定自动更新特征被激活。 再一次强调,最好的办法是在Init 方法中用代码设置这些属性。 Init 方法一些代码在list2 中。 在关闭类设计器之前,你可能想将 BufferModeOverride 属性换成 "5",“开放的表缓冲”以便移动记录指针的时候 , 自动更新不发生。 测试 CursorAdapter 的更新能力,把它作为一个实例,浏览游标,作一个更改, 然后提交 TableUpdate。 确定所作的更改被响应,调用 CursorAdapter 的 CursorRefresh 方法,再浏览一次。 处理ODBC错误(Handling ODBC Errors) 象使用本地 CursorAdapter一样 ,大多数的错误处理是按照 "传统的"方法—测试 TableUpdate 返回的值,如果失败,使用 AError 来判断原因。 不幸的是,更新冲突的检测也是ODBC连接类型 CursorAdapter 的一个问题。 本地 CursorAdapter 错误的解决办法是在 AfterUpdate 方法中设置错误处理代码, 但这种方法对ODBC型 CursorAdapter 是无效的,因为更新失败的时候,我们不是要处理VFP错误,而是ODBC错误。 因此,最好答案是使用一个存储过程,或在更新句柄中增加一些代码发送到服务器。 回想一下,为本地的 CursorAdapter 更新错误的解决办法是检查 _TALLY,看看是否有记录被更新。为ODBC连接的解决办法与本地CursorAdapter是相似的,但是我们不能使用 _TALLY,因为在远程数据检测上它是不可靠的。 我们可以使用SQL server的@@Rowcount 系统函数来判定记录是否被更新。 如果你要写一个 T- SQL 批量更新语句来更新一笔记录,你应该象下面这样写: --@custID and @oldContact set by earlier code or parameters UPDATE customers SET ContactName = @newContact WHERE CustomerID = @custID AND ContactName = @oldContact IF @@ROWCOUNT = 0 RAISERROR('Update failed.',16,1) RaisError T- SQL 函数使 VFP 接收一个ODBC错误 (1526 号), 在第一个参数里写错误信息.(另外二个参数指出严重和错误的状态) 在这种情况下, 当@@Rowcount=0的时候RaisError 被调用, 表明早先的 T- SQL 语句没影响任何的记录。 所有讨论的这一些表明你可以使用 CursorAdapter 的 BeforeUpdate 方法来描述发送到服务器用来更新的语句。 BeforeUpdate 方法接受五个参数, 有趣的是最后的二个 (cUpdateInsertCmd 和 cDeleteCmd) 建立了关联(注:原文the last two (cUpdateInsertCmd and cDeleteCmd) are interesting in that they are passed by reference. )。 在它们被送去数据源之前 , 这允许你修改指令。 在我们的例子中,我们使用这一个方法增加一个对@@Rowcount的测试然后调用 RaisError 。把下列代码写入 BeforeUpdate : LPARAMETERS cFldState, lForce, nUpdateType, ; cUpdateInsertCmd, cDeleteCmd IF nUpdateType = 1 THEN cUpdateInsertCmd = cUpdateInsertCmd + ; " if @@ROWCOUNT = 0 "+ ; "RAISERROR('Update Failed due to update " + ; "conflict.',16,1)" ENDIF 现在,为发送到后台数据库的每一行记录,用这段代码测试行是否被更新。 如果没更新, VFP 将会接收到错误,TableUpdate 将会失败,而且 AError 将会显示1526号错误,并显示预设的错误信息。 存在二个的问题。 首先,这是特别为 SQL server设置的;对其他的ODBC数据源 ( 比如ORACLE) ,这一段代码无效。 其次,这个错误信息是指一类错误,总是产生相同的 VFP 错误号, 而且在 VFP 中建立正确的错误处理句柄有一点困难。这一个问题可以通过在SQL SERVER服务器上建立通用的错误信息,每条信息对应着自己的唯一错误号码。 另外一个更好的解决方法是使用存储过程来执行更新,代替用VFP建立一个查询。 当然,采用存储过程的不利因素是不能使用VFP 的自动更新句柄来更新数据。 参数化设置 Parameterization 通过学习,你已经知道如何为 CursorAdapter 类的方法、属性添加命令。 基本上,在类中使用的每个事件(event)都有一组before和after方法, 比如 BeforeUpdate 和 AfterUpdate 。然而,没有 BeforeSelect 或 AfterSelect-替代它们叫做 BeforeCursorFill 和 AfterCursorFill,因为游标是用 SelectCmd 的结果来填充的。 BeforeCursorFill 方法接受三个参数, 而且返回 Boolean 值。 第一个参数 , lUseCursorSchema, 叙述 CursorSchema 属性是否控制游标的结构。 第二个参数 , lNoDataOnLoad,与视图的 NODATA 子句类似,只取回表结构,但是没有从数据源取回数据。 对于现在的讨论 , 第三参数 , cSelectCmd,是最有意思的。 我们已经提到过它 (象 BeforeUpdate 的 cUpdateInsertCmd 参数) ,先使用 SelectCmd 的当前设置。然而,如果你改变这一个参数的值,它不改变 SelectCmd 属性的值;取而代之的是,它的值改为被传给数据源的语句 。 举例来说, 假如你已经把 CursorAdapter 对象的 SelectCmd 设定为下列代码: SELECT CustomerID, CompanyName, ContactName, City, Region, Country FROM Customers 在呼叫 CursorAdapter 的 CursorFill 方法之前, BeforeCursorFill 方法的 cSelectCmd 参数会包含这个语句。 假如你在这一个方法中用下列代码: cSelectCmd = cSelectCmd + ; " WHERE CompanyName LIKE '" + ; this.cCompanyName + "%'" 这就导致实际的select命令总是包含如上面代码描述的where子句和 this.cCompanyName(自定义属性) 的当前值.因为它不改变 SelectCmd 的初始值, 你不必用任何特别的代码来确定在select命令中是否包含两个where子句。 参数设置第二部分 如果你以前用过视图,或 SQL pass through,那么你可能熟悉在参数前使用 "?" 字符。 这一个用法也适用于 CursorAdapter 。 下面的代码例子告诉你如何在 CursorAdapter 的 SelectCmd 属性中使用参数: This.SelectCmd = "SELECT * FROM Customers " + ; " WHERE CompanyName like ?lcMyVar " lcMyVar = 'C%' This.CursorFill() 最要紧的是确定变量 "lcMyVar" 在 CursorFill 方法被请求之前被定义。否则的话,VFP会提示找不到值,使用者会无所适从。 你可以使用 CursorAdapter 的属性作为参数,用来替代本地的变量。这样做有这样的优点:只要对象存在,属性也会存在,而且你可以通过使用access/assign方法来确定分配的值符合规则。 使用存储过程 在上面说过, 使用储存过程是一个突破错误处理限制的好办法。让我们逐步探究在ODBC CursorAdapter中使用存储过程的方法。这样我们就能感觉出手动处理 CursorAdapter 类的更新错误是多么复杂。 这一段是关于通过调用数据源的存储过程来代替自动执行的update、insert和delete命令。 这意味着你必须处理 UpdateCmd , InsertCmd 和 DeleteCmd 属性, 而且假设 SQL server上的 Northwind 数据库已经建立存储过程来提供这些功能。 例子, 让我们看一看一个简单存储过程的完整代码,你可以用它更新 Northwind 数据库的customer表的 ContactName 字段: --T-SQL code, not VFP CREATE PROCEDURE UpdateCustomerContact ( @CustomerID nchar (5), @ContactName nvarchar (30), @oldContact nvarchar (30) ) AS IF @CustomeriD IS NULL RAISERROR('CustomerID is a required parameter',16,1) ELSE UPDATE Customers SET ContactName = @contactName WHERE CustomerID = @customerID AND ContactName = @oldContact 为了节约空间,这一个存储过程没有包含全部的错误处理代码。 不管这个,已经有足够的代码来说明怎样在 CursorAdapter 类中执行更新。 幸运地, 建立 UpdateCustomerContact 存储过程作为更新命令,可以取代 BeforeUpdate 方法,用下面的代码: LPARAMETERS cFldState, lForce, nUpdateType, ; cUpdateInsertCmd, cDeleteCmd cUpdateInsertCmd = ; "EXECUTE UpdateCustomerContact '" + ; EVALUATE(this.Alias+".CustomerID") + "','" +; ALLTRIM(EVALUATE(this.Alias+'.ContactName'))+ ; "','" + ; OLDVAL('contactname',this.Alias)+"'" 这里,代码放在 cUpdateInsertCmd 参数里,覆盖了默认的update命令。我使用了evaluate函数,是为了cursor的名字是动态的,说明cursor的名字可以很容易被改变,但是代码不变。还有,我使用了OLDVAL 函数取回 ContactName 字段被修改前的值。如果在存储过程的where子句里需要旧的数据,那么这个函数是必需的。这有点像自动产生update的情况。 记住,在记录被实际更新之前 ,通过TableUpdate自动呼叫 BeforeUpdate 方法。 因此, 无论UpdateCmd当前的值是什么,这个方法程序(指UpdateCmd)被禁用,而且总是使用存储过程。 注意,你也可以使用已经讨论过的参数设置方法,使用 BeforeUpdate 方法。 这就要求你为 CursorAdapter 提供 UpdateCmd设置,但是, 不要在参数里用固定的代码,要用变量或者属性,并在它们前面加上“?”。 在这里需要注意的重要的一点是 cUpdateInsertCmd( 或对象的 UpdateCmd属性) 不能返回任何值。 进一步说,如果你从存储过程返回一个值,它就没有地方 "去" ,那么这个值就会永远丢失。因此,在存储过程中添加一个RaisError呼叫,以便在更新过程中发生任何错误( 例如不正确的参数或一个更新冲突) 的时候,你的代码能够作出反应,这样做是非常重要的。你可以通过测试 TableUpdate 返回值来捕获错误 ,或者用 AError 方法, 然后分析错误。 相似的代码也应该写进 BeforeInsert 和 BeforeDelete 方法,以便它们也调用存储过程,而不是调用设置好的“查询”。 为了节约空间,我将留下代码当做 "读者的练习." CursorAdapter with OLE DB 我们的下一个任务是看看如何通过 CursorAdapter 类使用OLE DB(对象连接与嵌入), 并且把它同我们用过的native和ODBC作一比较。OLE DB技术比ODBC有更多的处理能力, 而且能够连接的数据源类型比ODBC多。 CursorAdapter 通过嵌入ADO对象使用OLE DB,这是OLE DB技术标准的 COM 封装。 VFP 会自动地把ADO记录集 转换成一个 VFP 游标, 以及处理更新, 正如在早先的例子中讲到的一样。 第一件要做的事,当然还是建立一个新的 CursorAdapter 类,我们用代码建立一个。 开始建立一个新的程序,取名字 caADO.prg, 把下列代码添加进去: PUBLIC goCAADO as CursorAdapter goCAADO = CREATEOBJECT('caADO') BROWSE DEFINE CLASS caADO AS CursorAdapter oConn = NULL oRS = NULL Alias = "cCustADO" DataSourceType = "ADO" SelectCmd = "SELECT " + ; "CustomerID, CompanyName, ContactName, "+; "ContactTitle, Address, City, Country "+; "FROM Customers WHERE Customerid LIKE 'C%'" FUNCTION Init() This.DataSource = this.oRS This.oRS.ActiveConnection = this.oConn This.CursorFill() ENDFUNC ENDDEFINE 在这段代码中,我们将 DataSourceType 设为ADO而且把我们对Customers的查询加入 SelectCmd 。 当 DataSourceType 是ADO的时候, DataSource 属性必须包含有效的 RecordSet 或命令对象, 这依赖于你如何使用 CursorAdapter。 如果你不使用参数化查询 (就象前面例子中讲到的用 "?")那么你可以用记录集( RecordSet );否则,你必须使用命令对象,因为ADO已经代替了参数选择。 你的查询中的任何参数在命令对象中自动地被处理。 在这种情况下,我们使用 RecordSet 对象 , 但是应该注意我们必须提供一个“连接”对象。 在这两种情形中,我使用Access方法建立了关于这些对象的参考。 list3 中是Access方法的代码。 两个Access方法首先检查对象是否已经被建立。 如果没有,那么就继续创建对象。在使用 RecordSet 的情形,你只需要建立对象,剩下的由 CursorAdapter 来做。 用“连接”对象的情况下, 你必须提供连接字符串(连接句柄)并且打开连接,因为CursorAdapter 不为你打开连接。这是因为“连接”不是 CursorAdapter 的一个属性, 而是 RecordSet 对象的一个属性。 用OLE DB更新 如果不另外设定几个属性,这么简单的 CursorAdapter 是没有更新能力的。 把下面的代码放在定义类(define)的代码中,在init方法之前运行,将会允许自动更新: KeyFieldList = "CustomerID" UpdatableFieldList = ; "CompanyName, ContactName, ContactTitle, "+ ; "Address, City, Country" UpdateNameList = ; "CustomerID Customers.CustomerID, " + ; "CompanyName Customers.CompanyName, " + ; "ContactName Customers.ContactName, "+; "ContactTitle Customers.ContactTitle, " + ; "Address Customers.Address, "+; "City Customers.City, Country Customers.Country" Tables = "Customers" 然而, RecordSet 会建立它的默认的 CursorLocation 和 CursorType 属性。 如果不改变这些属性设置, RecordSet 最初是只读的,因此,你需要按以下方法修正 oRS_Access 方法: FUNCTION oRS_Access() as ADODB.RecordSet LOCAL loRS as ADODB.RecordSet IF VARTYPE(this.oRS)<>"O" THEN this.oRS = NULL loRS = NEWOBJECT("ADODB.Recordset") IF VARTYPE(loRS)="O" THEN loRS.CursorType= 3 && adOpenStatic loRS.CursorLocation = 3 && adUseClient loRS.LockType= 3 && adLockOptimistic this.oRS = loRS ENDIF ENDIF RETURN this.oRS ENDFUNC 为 RecordSet 加上这些代码以后, CursorAdapter 就能处理自动更新了。 CursorAdapter with XML 最后, 让我们建立使用XML作为数据源的 CursorAdapter。 这一节很有趣,因为XML文本不像通常的数据源。 同样,当数据源设置为XML的时候, CursorAdapter 不会自动建立 SQL 更新, 插入或删除命令。因此,这种类型的 CursorAdapter 需要大量的代码接收和更新数据。 在这一个例子中,我将使用 SQL server 2000 的 SQLXML 提供XML文本。 同时,因为SQLXML支持通过XML更新,我们将花一点时间写必要的代码来执行更新。 假设你已经配置了SQLXML,允许HTTP数据存取Northwind 数据库,而且允许用 UpdateGrams 对数据库进行更新。 在这里,我在 IIS 上面设置使用一个被称为 "nwind" 的虚拟目录存放 HTTP 数据。 因此,我的全部例子将会包含这个网址: http://localhost/nwind,经由 IIS 存取 SQLXML。 让我们开始建立一个新的程序,程序名字为caXML.prg,用下列代码(创建一个类): PUBLIC oCAXML as CursorAdapter SET MULTILOCKS ON && need for table buffering oCAXML = CREATEOBJECT('xcXML') BROWSE NOWAIT DEFINE CLASS xcXML AS CursorAdapter DataSourceType = "XML" Alias = "xmlCursor" UpdateCmdDataSourceType = "XML" InsertCmdDataSourceType = "XML" DeleteCmdDataSourceType = "XML" BufferModeOverride = 5 *custom properties oXMLHTTP = NULL oXMLDOM = NULL cServer = "localhost" cVDir = "nwind" ENDDEFINE 不同于通常的 DataSourceType 和Alias(别名)属性设置,这是我们第一次看见 xxxCmdDataSourceType 属性。 因为这是一个XML类型的 CursorAdapter,如果你想让它用来更新源数据,这些属性就必须进行设置 。推荐在这个类中使用 oXMLHTTP 和 oXMLDOM 属性,将会在下面做详细说明。 接收XML数据 在考虑 CursorAdapter 的更新能力之前,我们先研究怎样从SQLXML 服务器接收文本。 首先,一个简单的select命令不能工作,我们必须建立通用的 SelectCmd 。 这在 Init 方法中很容易做到,同时,在INIT中调用 CursorFill 方法,如以下代码: FUNCTION INIT() as Boolean LOCAL llRetVal, lcMsg, laErr[1] this.SelectCmd = "this.GetXml()" llRetVal = THIS.CursorFill() IF NOT llRetVal THEN AERROR(laErr) lcMsg = "Cursor was not filled!" IF NOT EMPTY(laErr[2]) THEN lcMsg = lcMsg + CHR(13) + laErr[2] ENDIF MESSAGEBOX(lcMsg,16,"XMLCursorAdapter Test") ENDIF RETURN llRetVal ENDFUNC 这一段代码建立 SelectCmd 作为本地用的方法,代替 SQL 命令。 以前的例子中没有这样做过,对任何 CursorAdapter 类,不管什么类型,都是合法的。然而,当你使用一个本地的方法作为 SelectCmd 的时候,你必须也提供代码用来更新,插入和删除, 因为如果不是一条SQL语句的话, VFP 是不会自动处理的。 当我们在 Init() 中使用 CursorFill 的时候,GetXML 方法被调用。 数据源设定为XML的时候,GetXML 方法必须返回只包含一个表的最终文本。 如果它包含多个表,你会得到料想不到的结果。 GetXML 方法在list4 中列示。 GetXML开始于提交一个 MSXML2.XMLHTTP COM 对象。 这个对象处理所有 HTTP 通信,包括发送请求到服务器并且返回结果。 你可以看到,oXMLHTTP 实例对象是被设计好的Access方法控制的,这样设计是为了防止连续地创建和破坏 COM 服务器。 然后,你可以看到我们的典型的SELECT描述, 除了LIKE子句有点稍稍不同以外。 HTTP 需要我们当十六位百分比字符显示到达25%的时候退出显示(指进度显示), 在 SQL server接收查询之前 , 这个值将会变成单一的百分比符号。 然后,代码用具体的查询要求来设置URL,并且通过HTTP发送URL到SQL SERVER服务器。SQL SERVER接收到这个查询,并且处理它,以XML方式返回值。这是因为我们已经在“查询”中包含了FOR XML子句。在这个例子中,XML的根元素叫做 "results" 。你从查询字符串中可以看出。这是按照你的习惯配置的。 此时, lcRetXML 包含一个来自 SQL server的XML数据流。 既然 GetXML 方法被 VFP 作为 SelectCmd 调用,你可以从 GetXML 方法中返回这个变量的内容,而且 VFP 将会把数据流转换成一个 VFP 游标。 你可以通过执行 caXML 程序测试。 会出现一个包含返回的XML文本内容的浏览窗户。 使用调试工具一步一步查看 GetXML 方法,可以看到在被转换到一个 VFP 游标并释放之前 , lcRetXML 变量的值是XML文本。 更新XML数据 下一个步骤是决定该如何使这个游标可更新,以便所作的更改能被发送回 SQLXML 服务器。 SQLXML 能使用一个特别的XML文本,即 UpdateGram, 利用它直接把更新发送到数据库。 在 VFP7 中,可以用 XMLUpdateGram 函数建立这个文本。 用VFP 8 的 CursorAdapter , UpdateGram 属性会自动建立。 第一个步骤是设置 updatable 属性并且建立一个更新指令。 在类定义的开始设置这些属性,并在CursorAdapter的init事件中增加一行代码,提供调用更新命令的方法。 KeyFieldList = 'customerid' Tables = 'customers' UpdatableFieldList = ; "companyname, contactname, contacttitle, "+; "address, city, country " UpdateNameList= ; "customerid customers.customerid, " + ; "companyname customers.companyname, " + ; "contactname customers.contactname, " + ; "contacttitle customers.contacttitle, " + ; "address customers.address, " + ; "city customers.city, country customers.country" FUNCTION INIT() as Boolean LOCAL llRetVal, lcMsg, laErr[1] this.UpdateCmd = "this.UpdateXML()" this.SelectCmd = "this.GetXML()" ** balance of code skipped... 注意,我们已经在init方法中替换了属性表中的 UpdateCmd 和 SelectCmd 设置,实际上最终结果是一样的。 不管怎样, 这一段代码的第一部份现在看起来很熟悉,在这里我们设置了 KeyFieldList ,table, UpdatableFieldList 和 UpdateNameList 属性。 没有这些属性设置,就不可能创建 UpdateGram 。 然后,我们建立 UpdateXML 方法作为 CursorAdapter 的 UpdateCmd 。 没有参数传递给 UpdateXML 方法,所以,所有决定更新的工作必须这个方法里处理。还有,因为XML 类型的 CursorAdapter 没有默认的更新机制, 你必须写代码把更新传递给XML数据源。 在这一段代码中,使用 XMLHTTP 对象传递更新到服务器。 通过LoadXML方法加载 UpdateGram 属性的内容到 XMLDOM( 在ACCESS方法中) 之内,打开到服务器的连接,设定请求的内容为XML, 然后发送 XMLDOM。所有的结果通过 XMLHTTP 对象的 ResponseText 属性返回,接着装载到 XMLDOM 中,并且分析错误信息。 如果没有提示错误,更新已经成功,而且过程结束。然而,如果有错误, 就会产生符合语法的错误信息文本,并且包含在一个自定义的ERROR函数中,这样 TableUpdate 函数就能找到更新失败的原因。 如果没有这些代码, 即使有问题的时候,TableUpdate 总是返回成功信息。 测试一下这段代码,运行 caXML 程序,在游标中更改一个字段, 然后在命令窗口中发出 TableUpdate。 如果 TableUpdate 成功, 你应该能在服务器上看到更新结果。如果 TableUpdate 失败,你需要用 AError 函数接收 SQL server产生的错误信息。 如果你对 UpdateGram 的内容感到好奇, 你可以一步步查看类的 UpdateXML 方法,并且查看 UpdateGram 属性的内容。 然而,如果你不到有关数据变化的方法中 (如 UpdateCmd , InsertCmd 或 DeleteCmd 属性内容) ,你也不能完全弄明白 UpdateGram 属性的内容。 list6显示当ContactName字段的客户ID被更改为‘CACTU' 后UpdateGram的内容。 正如你看到的,SQLXML 能够读取这个文本并且能够很容易地建立一个Update-SQL 语句, 然后发送到 SQL server。 updg:sync(同步更新)元素使更新立即发生;因此,如果你有多个表需要更新,你可以把它们关联起来放到一个 UpdateGram中, 确定把他们封装进这个元素,执行一次就可以全部更新。 结束语 在这一篇文章中,我们涉及了许多方面,讲了新的 CursorAdapter 类的四种表现形式。 你已经学到了如何通过 DataEnvironment 、 CursorAdapter buileder、类设计器、和PRG建立 CursorAdapter 。 你也已经了解了建立本地、ODBC、OLE DB或XML型 CursorAdapter 类的基本方法, 并且怎样使这些类可更新。 下一个步骤是考虑如何把这些类应用到你的程序中。 对我来说,觉得 CursorAdapter 应用到任何程序的 UI 层中效果很好,同时应用于需要大量过程代码的许多商业开发。CursorAdapter并不是一个最好的选择对象用于层来传递数据。因为它把所有的来自数据源的数据转化成一个不便于携带的游标。 然而,在一个使用 CursorAdapter 类进行商业开发的方案中,它能接收来自数据源的数据, 然后用标准的 VFP 命令和函数处理数据,这是因为它产生的是一个 VFP 游标。 数据可以被转换为更一个较适当的类型,例如XML。 CursorAdapter 的另一个有利条件是通用的 OOP 接口,不管需要存取的数据是什么类型。 甚至用XML,需要大量代码来使类可更新,我们仍然可以用使用 CursorFill取回数据,用 TableUpdate 更新数据, 而且用 AError 返回错误, 像使用其他类型的 CursorAdapter一样。 基于一个事先的考虑或者计划,你想建立一个可以重复使用的CursorAdapter类。 这些类可以在程序之间重复使用或者在同一程序内部混合使用,来形成一种你的程序处理数据的标准方法。

    最新回复(0)