您现在的位置: 中国男护士网 >> 考试频道 >> 计算机等级 >> 二级辅导 >> VFP >> 辅导 >> 正文    
  深入CursorAdapter(四) 【注册男护士专用博客】          

深入CursorAdapter(四)

www.nanhushi.com     佚名   不详 

  考试大计算机等级站整理:

  在附件 SFDataClasses.vcx 中的这个数据和环境类要比 SFCursorAdapter 简单的多。但它增加了一些非常有用的功能:

  ×× GetData 方法会调用所有在这个数据环境类里面的 SFCursorAdapter 成员类的 GetData 方法,这样你就不需要自己去一个个的调用它们。与此类似的是,Requery 方法和 Update 方法也会调用每个 SFCursorAdapter 成员类的 Requery 和 Update 方法。

  ×× 象 SFCursorAdapter 一样,SetConnection 方法会把 DataSource 设置为一个 ADO Recordset,并把这个 Recordset 的 ActiveConnection 属性设置为一个 ADO Connection 对象。不过,它还会调用所有 UseDEDataSource 属性被设置为 .F. 的 SFCursorAdapter 成员类的 SetConnection 方法。

  ×× 它提供了一些简单的错误处理(cErrorMessage 属性会被填入错误信息)

  ×× 它有一个 Release 方法。

  现在我们看看这个类的一对方法。GetData 非常简单:如果这个数据环境类的任何成员对象拥有 GetData 方法,则调用该方法:

  lparameters tlNoData
  local loCursor, ;
  llReturn
  for each loCursor in This.Objects
  if pemstatus(loCursor, 'GetData', 5)
  llReturn = loCursor.GetData(tlNoData)
  if not llReturn
  This.cErrorMessage = loCursor.cErrorMessage
  exit
  endif not llReturn
  endif pemstatus(loCursor, 'GetData', 5)
  next loCursor
  return llReturn

  SetConnection 方法稍微复杂一点:如果它的任何成员对象有 SetConnection 方法、并且该成员对象的 UseDEDataSource 属性被设置为 .F.,则调用该成员对象的 SetConnection 方法;然后,如果有任何一个 CursorAdapter 对象的 UseDEDataSource 属性被设置为了 .T.,则使用类似于 SFCusrorAdapter 中的那样的代码来设置自己的 DataSource:

  lparameters tuConnection
  local llSetOurs, ;
  loCursor, ;
  llReturn
  with This

  * Call the SetConnection method of any CursorAdapter that isn't using our
  * DataSource.

  llSetOurs = .F.
  for each loCursor in .Objects
  do case
  case upper(loCursor.BaseClass) <> 'CURSORADAPTER'
  case loCursor.UseDEDataSource
  llSetOurs = .T.
  case pemstatus(loCursor, 'SetConnection', 5)
  loCursor.SetConnection(tuConnection)
  endcase
  next loCursor

  * If we found any CursorAdapters that are using our DataSource, we'll need to
  * set our own DataSource.

  if llSetOurs
  do case
  case .DataSourceType = 'ODBC'
  .DataSource = tuConnection
  case .DataSourceType = 'ADO'
  .DataSource = createobject('ADODB.RecordSet')
  .DataSource.ActiveConnection = tuConnection
  endcase
  endif llSetOurs
  endwith

  TestDE.prg 做了一个演示,它使用 SFDataEnvironment 作为容器,该容器中有一对 SFCursorAdapter 类。因为这个例子用的是 ADO,所以每个 SFCursorAdapter 都需要它自己的 DataSource,因此它们的 UseDEDataSource 属性都被设置成了 .F.(默认设置)。注意:只要简单的调用一下 SFDataEnvironment 的 SetConnection 方法就能搞定为每个 CursorAdapter 设置好 DataSource 属性的事情。

  local loConn as ADODB.Connection
  loConn = createobject('ADODB.Connection')
  loConn.ConnectionString = 'provider=SQLOLEDB.1;data source=(local);' + ;
  'database=Northwind;uid=sa;pwd='
  && change password to appropriate value for your database
  loConn.Open()
  set classlib to SFDataClasses
  loDE = createobject('SFDataEnvironment')
  with loDE
  .AddObject('CustomersCursor', 'SFCursorAdapter')
  with .CustomersCursor
  .Alias = 'Customers'
  .SelectCmd = 'select * from customers'
  .DataSourceType = 'ADO'
  endwith
  .AddObject('OrdersCursor', 'SFCursorAdapter')
  with .OrdersCursor
  .Alias = 'Orders'
  .SelectCmd = 'select * from orders'
  .DataSourceType = 'ADO'
  endwith
  .SetConnection(loConn)
  if .GetData()
  select Customers
  browse nowait
  select Orders
  browse
  else
  messagebox('Could not get the data. The error message was:' + ;
  chr(13) + chr(13) + .cErrorMessage)
  endif .GetData()
  endwith
  loConn.Close()

  可重用数据类

  现在我们已经有了可用的 CursorAdapter 和 DataEnvironment 的子类,让我们来谈谈可重用数据类的事情。

  一件 VFP 程序员们已经向 Microsoft 要求了很久的事情是可重用数据类。例如,你可能有一个表单和一个报表,它们使用的是完全相同的一套数据,然而你却不得不重复的去手动向数据环境中添加一个个表或者视图——因为数据环境是不可重用的。某些程序员(以及几乎所有的应用程序框架提供商)采用了通过代码来建立可重用数据环境(那时候数据环境是不能可视化的建立子类的)的方法,并且在表单上使用一个 “loader”对象来建立 DataEnvironment 子类的实例。不管怎么说,这种方法总不是那么正规,并且无法用于报表。

  现在,在 VFP8 里,我们不仅拥有了建立“能够向任何需要数据的对象提供来自任何数据源的数据”的可重用数据类的能力,还有了建立“能够寄宿数据类的”数据环境类的能力。在我写这篇文章的时候,你还不能在报表中使用 CursorAdapter 或者 DataEnvironment 的子类,不过你可以编程的添加 CursorAdapter 子类(例如把这些代码写在 DataEnvironment 的 INIT 方法中)来利用可重用类的优点。

  让我们为 Northwind 数据库的 Customers 和 Orders 表建立一些数据类。CustomersCursor (在 NorthwindDataClasses.vcx 中)是 SFCursorAdapter 的一个子类,其属性如表1:

  表 1. CustomersCursor 的属性

  属性 值
  Alias Customers
  CursorSchema CUSTOMERID C(5), COMPANYNAME C(40), CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60),
  CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), PHONE C(24), FAX C(24)
  KeyFieldList CUSTOMERID
  SelectCmd select * from customers
  Tables CUSTOMERS
  UpdatableFieldList CUSTOMERID, COMPANYNAME, CONTACTNAME, CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX
  UpdateNameList CUSTOMERID CUSTOMERS.CUSTOMERID, COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME, CONTACTTITLE CUSTOMERS.CONTACTTITLE, ADDRESS CUSTOMERS.ADDRESS, CITY CUSTOMERS.CITY, REGION CUSTOMERS.REGION, POSTALCODE CUSTOMERS.POSTALCODE, COUNTRY CUSTOMERS.COUNTRY, PHONE CUSTOMERS.PHONE, FAX, CUSTOMERS.FAX

  你不会以为我会是手动在属性窗口中输入所有这些属性的值吧?当然不是!我是用 CursorAdapter 的生成器来干的。这里的技巧是打开“Use connection settings in builder only(只使用在生成器中的连接设置)”,填入连接信息以获得一个活动连接,再填好 SelectCMD 以后,最后再用生成器来生成其它的属性设置。

  现在,任何时候你需要 Northwind 的 Customers 表中的数据,只要简单的放一个 CustomersCursor 类就够了。当然,我们还没有定义任何连接信息,不过做到这样就已经很不错了,有了这个类就不需要担心怎么获得数据(ODBC、XML、ADO)、使用哪种数据引擎(比如 SQL Server、Access 以及 VFP 8中都有 Northwind 数据库)之类的事情了。

  不过,要注意的是这个 Cursor 对付的是 Customers 表中所有的记录。可有时候,你又只想要一个客户的数据。那么,CustomerByIDCursor 是 CustomersCursor 的一个子类,它的 SelectCmd 已经被改成 “Select * from customers where customerid = ?pcustomerid”,还有下面增加的 INIT 方法的代码:

  lparameters tcCustomerID
  dodefault()
  This.AddParmeter('pCustomerID', tcCustomerID)

  这段代码会建立一个叫做 pCustomerID 的参数(它跟在 SelectCmd 中指定的是同一个),并且被设置成传递进来的任何值。如果没有值被传递进来的话,那么使用 GetParameter 方法来为这个参数返回一个对象,并在调用 GetData 之前设置它的 value 属性。

  OrdersCursor 类类似于 CustomersCursor,只是它返回的是 Orders 表中的所有数据,而 OrdersForCustomerCursor 是它的一个子类,用于返回一个指定客户的所有订单。

  要测试一下的话,请运行 TestCustomersCursor.prg,它会从 SQL Server 版本的 Northwind 数据库中 Customers 表的一个客户,然后做到 Access 版本的 Northwind 数据库所做的同样的事情。这个示例演示了怎样不为类指定连接信息,这个类自己就能灵活的完成任务,因此,它的可重用性是很强的。

  示例:表单

  现在我们已经有了一些可重用类,让我们来用用它们。首先,我们来建立 SFDataEnvironment 的一个子类 CustomersAndOrdersDataEnvironment (哈哈,名字可有够长的,D.H牌冰糖葫芦!),它包含着一个 CustomerByIDCursor 类和一个 OrdersForCustomerCursor 类。由于我们希望在打开表之前设置连接信息,因此把它的 AutoOpenTables 属性设置为了 .F.,而且把前面两个 CursorAdapter 的 UseDEDataSource 属性都设置为了 .T.。现在,这个数据环境类已经可以被用来在某个表单中显示关于一个指定客户的信息以及他的订单了。

  让我们来建立这么一个表单。附件中的 CustomerOrders.scx 表单的 DEClass 和 DEClassLibrary 属性已经被设置为了CustomersAndOrdersDataEnvironment 和 NorthwindDataClasses.vcx,这样就用上了我们的可重用数据环境类。这个表单的 Load 方法里面的代码有点多,不过这是因为它要支持 ADO、ODBC、以及 XML 数据源,并且它还要建立自己的连接,所以大多数代码都是处理这些问题的。如果它只支持一种数据源的话,比如只用 ODBC,再比如由另一个对象来管理连接(实际的应用程序开发中常常就是这样的),代码就会简单多了:

  with This.CustomersAndOrdersDataEnvironment
  * 获得连接
  lnHandle = oApp.oConnectionMgr.GetConnectionHandle()
  .SetConnection(lnHandle)

  * 指定从 CustomerID 文本框中取得 cursor 参数的值
  loParameter = ;
  .CustomerByIDCursor.GetParameter('pCustomerID')
  loParameter.value = '=Thisform.txtCustomerID.value'
  loParameter = ;
  .OrdersForCustomerCursor.GetParameter('pCustomerID')
  loParameter.value = '=Thisform.txtCustomerID.value'

  * 建立一个空的 cursor,如果失败的话则显示错误信息

  if not .GetData(.T.)
  messagebox(.cErrorMessage)
  return .F.
  endif not .GetData(.T.)
  endwith

  这段代码用上了那两个 CursorAdapter 对象的 GetParameter 方法来把 pCustomerID 参数设置为表单上一个文本框中的值。注意在值里面用到的'=',它表示在你需要 value 属性的时候再去运算它的值,所以我们实际上有了一个动态的参数(这样就顺应了当用户在文本框中输入了新的值以后要将改动反应到参数中去的需要)。调用 GetData 方法是为了建立一个空的 Cursor,这样才能安顿那些数据绑定的控件。

  txtCustomerID 文本框没有绑定什么数据。它的 Valid 方法先调用数据环境的 Requery 方法,然后再调用表单的 Refresh 方法。这样,当用户输入一个新的客户ID的时候,就能够 Requery 那个 Cursor,接着表单上其它控件也会被刷新。表单上的其它文本框被绑定到由 CustomersByIDCursor 对象建立的 Customers cursor 的字段中。那个 Grid 被绑定到由 OrdersForCustomerCursor 对象建立的 Orders Cursor。

  运行这个表单,并输入一个 Customer ID 为“ALFKI”(见图1)。当你按下 Tab 键跳出这个文本框的时候,你会看到该客户的地址信息以及他的订单就出现了。试着改动一些这个客户的数据或者订单数据,然后关闭表单再打开,再输入一次“ALFKI”,你会看到你没做什么工作这些改动就都已经被写到后台数据库中了。

  酷吧,嗯?跟建立一个基于本地表或者视图的表单相比,并没多多少工作。更棒的是:试试把定义在 Load 方法中的 ccDATASOURCETYPE 常量改变为 “ADO”或者“XML”,然后这个表单无论是看起来还是实际工作起来都跟没改过之前一摸一样(如果你想用 XML,你需要象上个月的文章中所说的那样为 Northwind 数据库设置一个 SQLXML 虚拟目录,并把本月附件中的 XML 模板文件拷贝到那个目录里)。

  示例:报表

  我们来试试报表。这里最大的问题是:与表单不同,我们既不能告诉报表去使用一个数据环境子类,也不能拖放一个 CursorAdapter 子类到数据环境中去。所以我们不得不向报表放入一些代码以将 CursorAdapter 添加到数据环境。尽管从逻辑上来看应该把这些代码放到报表数据环境的 BeforeOpernTables 事件中去,不过事实上这样做却是行不通的,因为——由于某些我不能理解的原因—— BeforeOpenTables 事件只会在你预览报表的每一页的时候才会触发。所以,我们只好把代码放在 Init 方法里。因为演示的需要,报表 CustomerOrders.frx 跟表单 CustomerOrders.scx 一样,要比实际开发的情况下会用到的代码更复杂一些。如果没有这些演示的需求的话,实际上可以简化到下面这样:
  with This
  set safety off

  * 获得连接
  .DataSource = oApp.oConnectionMgr.GetConnectionHandle()

  * 建立客户和订单的 CursorAdapter 对象
  .NewObject('CustomersCursor', 'CustomersCursor', ;
  'NorthwindDataClasses')
  .CustomersCursor.AddTag('CustomerID', 'CustomerID')
  .CustomersCursor.UseDEDataSource = .T.
  .NewObject('OrdersCursor', 'OrdersCursor', ;
  'NorthwindDataClasses')
  .OrdersCursor.AddTag('CustomerID', 'CustomerID')
  .OrdersCursor.UseDEDataSource = .T.

  * 取得数据,如果失败,则显示错误信息
  if not .CustomersCursor.GetData()
  messagebox(.CustomersCursor.cErrorMessage)
  return .F.
  endif not .CustomersCursor.GetData()

  if not .OrdersCursor.GetData()
  messagebox(.OrdersCursor.cErrorMessage)
  return .F.
  endif not .OrdersCursor.GetData()

  * 从 Customers 设置一个与 Orders 的关系
  set relation to CustomerID into Customers
  endwith
  这里的代码比表单示例的要多一些,这是因为我们不能使用自定义的数据环境类导致不得不自己手动编码来代替。
  现在,我们怎样才能尽可能简单的就把那些字段放到报表上去呢?由于 CursorAdapter 对象是我们用代码在运行时才添加到数据环境中去的,在设计时就没办法享受到拖放字段到报表上的方便了。这里有个小技巧:建立一个会建立这些 Cursor 的 PRG文件,并让这些 Cursor 处于有效范围内(可以采用挂起 PRG 的运行或者把 CursorAdapter 对象声明成 Public 的办法),然后使用快速报表功能来把那些字段放到报表上,这样报表控件的大小也设置好了。
  当你预览报表的时候该报表的情况。如果结合表单一起使用的话,你可以试试改动表单数据环境中的 #DEFINE 语句来换用其它类型的数据源。

  总结

  我们对新的 CursorAdapter 基础类的研究就到这里了。我个人对 CursorAdapter 的出现感到非常的兴奋,并计划给我的应用程序框架中的数据处理部件升升级以更充分的利用它的优点。

  下个月,我们将研究一下在 VFP 8 中通过 Try ... Catch ... Finally ... EndTry 命令大大增强了的错误处理方式以及新的 Exception 基础类。

 

文章录入:杜斌    责任编辑:杜斌 
  • 上一篇文章:

  • 下一篇文章:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
     

    联 系 信 息
    QQ:88236621
    电话:15853773350
    E-Mail:malenurse@163.com
    免费发布招聘信息
    做中国最专业男护士门户网站
    最 新 热 门
    最 新 推 荐
    相 关 文 章
    没有相关文章
    专 题 栏 目

      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)                            【进男护士社区逛逛】
    姓 名:
    * 游客填写  ·注册用户 ·忘记密码
    主 页:

    评 分:
    1分 2分 3分 4分 5分
    评论内容:
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。