VFP CursorAdapter 起步三(作者:Doug Hennig 译者:fbilo)
可重用数据类
VFP 的程序员们想要一个可重用的数据类已经很久了。尽管在过去的版本中也有许多解决这个问题的办法,不过总是有点美中不足。现在在 VFP 8里,我们有了真正的可重用数据类。这个月,Doug 为我们演示了怎样通过建立 CursorAdapter 和 DataEnvironment 的子类来建立可重用的数据类、以及怎样在表单和报表中使用它们。
正文
××
在过去的两期杂志中,我们讨论了在 VFP 8 中新增的 CursorAdapter 基础类。我个人的观点是,这是 VFP 8 中最重要的改动之一,因为它向我们提供了一个对象SQL Server这样的非VFP数据源的简单易用、统一的接口。此外,如你本月所能见到的那样,它们还形成了可重用数据类的基础。
在讲述可重用数据类之前,让我们先来看一下我建立的一些 CursorAdapter 和 DataEnvironment 的子类,我给它们增加了一些额外的功能,它们将成为我们的可重用数据类的起点。
SFCursorAdapter
***************
SFCursorAdapter (在附件 SFDataClasses.vcx 中) 是 CursorAdapter 的一个子类,它拥有一些额外增加的功能,如下:
※ 它可以自动处理参数化查询:你可以静态(一个常量)也可以动态(一个表达式,例如“=Thisform.txtName.value”,当 Cursor 被打开或者刷新的时候,这个表达式会被运算)的定义一个参数值。
※ 它可以在 Cursor 被打开以后自动在该 Cursor 上建立索引。
※ 对于 ADO,它还会执行一些特殊的工作,例如把 DataSource 属性设置为一个 ADO RecordSet,把这个 RecordSet 的 ActiveConnection 属性设置为一个 ADO Connection 对象,当用到一个参数化查询的时候,它还会建立一个 ADO Command 对象并把这个对象传递给 CursorFill 方法。
※ 它提供了简单的错误处理(cErrorMessage 属性里会有错误的信息)。
※ 它还有 CursorAdapter 中缺少的 Update 和 Release 方法。
这个类的 INIT 方法建立两个集合(使用新的 Collection 基础类,它是维护某些东西的集合用的),一个是为 SelectCmd 属性可能会用到的参数而准备的,另一个是用于在 Cursor 被打开以后应该自动建立的标记。它还会 SET MULTILOCK ON,因为这是 CursorAdapter Cursor 的需求。
This.oParameters = CreateObject('Collection')
This.oTags = CreateObject('Collection')
Set multilocks on
AddParameter 方法象 parameters 集合添加一个参数。给这个方法传递参数的名称(这个名称应该与该参数出现在 SelectCmd 属性中的那个名称相一致),根据需要也可以付上参数的值(如果你现在不给它传递参数的值,也可以以后再调用 Getparameter 方法来传递)。这段代码演示了一对 VFP 8 中的新功能:新的 empty 基础类,它没有任何属性、事件或者方法,因此是建立一个轻量级的对象的理想选择;还有 AddProperty() 函数,它的作用跟 AddProperty 方法类似,区别是它用于那些没有这个方法的对象。
lparameters tcName, tuvalue
local loParameter
loParameter = createobject('Empty')
addproperty(loParmeter, 'Name', tcName)
addproperty(loParmeter, 'value', tuvalue)
This.oParameters.Add(loParameter, tcName)
使用 GetParmeter 方法来返回一个特殊的 parameter 对象——通常是用在需要设置用于参数的值的时候。
lparameters tcName
local loParameter
loParameter = This.oParameters.Item(tcName)
return loParameter
SetConnection 方法用于将 DataSource 属性设置为希望的连接。如果 DataSourceType 是 “ODBC”,就给这个方法传递一个连接句柄;如果是“ADO”,DataSource 必须是一个ADO Recordset 对象,而且该对象的 ActiveConnection 属性必须要设置为一个活动 ADO Connection 对象,所以,我们需要向 SetConnection 方法传递这个 ADO Connection 对象, SetConnection 会建立一个 RecordSet,并且把这个 RecordSet 的 ActiveConnection 设置为被传递的 ADO Connection 对象。
lparameters tuConnection
with this
do case
case .DataSourceType = 'ODBC'
.DataSource = tuConnection
case .DataSourceType = 'ADO'
.DataSource = Createobject('ADODB.RecordSet')
.DataSource.ActiveConnection = tuConnection
endcase
endwith
为了建立 Cursor,我们调用 GetData 方法而不是 CursorFill 方法,因为 GetData 能够自动处理参数和错误。如果你想要建立一个不带数据的 Cursor,那么就给 GetData 方法传递一个 .T.。这个方法建立的第一样东西,是与定义在 parameters 集合中的参数们同名的私有变量(在这里调用了 GetParametervalue 方法,该方法会返回参数对象的值,如果该对象的值是一个以“=”开头的表达式,那么返回的将是运算该表达式之后所获得的值。)下一步,如果我们是在使用 ADO 并且已经有了一些参数,这段代码会建立一个 ADO Command 对象,并把该对象的 ActiveConnection 属性设置为 Connection 对象,然后把这个 Connection 对象传递给 CursorFill 方法——这是 CursorAdapter 处理 ADO 参数化查询的需要。如果我们不是在用 ADO 、或者没有任何参数,那么代码会简单的调用 CursorFill 来填充 Cursor。注意,如果给 GetData 方法传递了 .T.,并且 CursorSchema 已经被填写好了,那么就是告诉 GetData 方法去使用 CursorSchema(这也是我想让 CursorAdapter 基类拥有的功能)。现在如果 Cursor 被建立起来了,代码会调用 GreateTags 方法来为该 Cursor 建立想要的索引;如果 Cursor 没有被建好,那么它会调用 HandleError 方法来处理任何发生了的错误。
lparameters tlNoData
local loParameter, ;
lcName, ;
luvalue, ;
llUseSchema, ;
loCommand, ;
llReturn
with This
* tlNoData参数指定是否要向 Cursor 中填充数据,如果要填充的话,
* 我们就要把建立存储所有参数的变量的活在这里就做掉而不是放到一个其它的方法中去。
* 因为我们希望这些变量的有效范围是私有的。
if not tlNoData
for each loParameter in .oParameters
lcName = loParameter.Name
luvalue = .GetParametervalue(loParameter)
store luvalue to (lcName)
next loParameter
endif not tlNoData
* 如果我们正在使用 ADO,并且有了一些参数,那么就需要有一个处理这些参数的 Command对象
llUseSchema = not empty(.CursorSchema)
if '?' $ .SelectCmd and (.DataSourceType = 'ADO' or (.UseDEDataSource and ;
.Parent.DataSourceType = 'ADO'))
loCommand = createobject('ADODB.Command')
loCommand.ActiveConnection = iif(.UseDEDataSource, ;
.Parent.DataSource.ActiveConnection, .DataSource.ActiveConnection)
llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions, loCommand)
else
* 填充这个 cursor.
llReturn = .CursorFill(llUseSchema, tlNoData, .nOptions)
endif '?' $ .SelectCmd ...
* 如果 Cursor 建立成功,则为之定义所有的 Tag,否则则处理发生的错误。
if llReturn
.CreateTags()
else
.HandleError()
endif llReturn
endwith
return llReturn
还有一些方法这里我们就不说了,你可以自己去研究它们。HandleError 方法使用 Aerror() 来判断发生了什么错误,并把错误数组的第二个元素放到 cErrorMessage 属性中去。Requery 方法与 GetData 类似,不过它是用来刷新 Cursor 中的数据。调用这个方法而不是 CursorRefresh 方法的原因就象 GetData 一样:它能够处理参数和错误。Update 方法很简单:它就是调用 TableUpdate() 来提交当前数据源的更新,如果提交更新失败,则调用 HandleError 方法来处理错误。AddTag 用于在 Cursor 被建立后将你想要建立的索引的信息添加到 Tags 集合中,而 GetData 方法会调用的 CreateTags 方法则会在自己的 Index ON 语句中用到这个集合中的信息。
这里是使用这个类的一个例子,是从附件中的 TestCursorAdapter.prg 中拿来的。它从 SQL Server 自带的 Northwind 数据库的 Customers 表中取得数据。它的 SelectCmd 属性里是一个参数化查询的 Select 语句,向你演示了怎样用 AddParameter 方法来处理参数,以及怎样用 AddTag 方法来自动地为 Cursor 建立索引标识。
local loCursor as SFCursorAdapter of SFCursorAdapter, ;
loConnMgr as SFConnectionMgrODBC of SFRemote, ;
loParameter as Empty
lnHandle = sqlstringconnect('driver=SQL Server;server=(local);' + ;
'database=Northwind;uid=sa;pwd=;trusted_connection=no')
&& change password to appropriate value for your database
loCursor = newobject('SFCursorAdapter', 'SFDataClasses')
with loCursor
.DataSourceType = 'ODBC'
.Alias = 'Customers'
.SelectCmd = 'select * from customers where country = ?pcountry'
.SetConnection(lnHandle)
.AddParameter('pcountry', 'Brazil')
.AddTag('CustomerID', 'CustomerID')
.AddTag('Company', 'upper(CompanyName)')
.AddTag('Contact', 'upper(ContactName)')
if .GetData()
messagebox('Brazilian customers in CustomerID order')
set order to CustomerID
go top
browse
messagebox('Brazilian customers in Contact order')
set order to Contact
go top
browse
messagebox('Canadian customers')
loParameter = .GetParameter('pcountry')
loParameter.value = 'Canada'
.Requery()
browse
else
messagebox('Could not get the data. The error message was:' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()
* Now try to do an invalid SELECT statement.
.SelectCmd = 'select * from customersx'
if .GetData()
browse
else
messagebox('Could not get the data. The error message was:' + ;
chr(13) + chr(13) + .cErrorMessage)
endif .GetData()
endwith
sqldisconnect(lnHandle)
SFDataEnvironment
*****************
在附件 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”,你会看到你没做什么工作这些改动就都已经被写到后台数据库中了。
此主题相关图片如下:
图1、
酷吧,嗯?跟建立一个基于本地表或者视图的表单相比,并没多多少工作。更棒的是:试试把定义在 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 的办法),然后使用快速报表功能来把那些字段放到报表上,这样报表控件的大小也设置好了。
图2展示了当你预览报表的时候该报表的情况。如果结合表单一起使用的话,你可以试试改动表单数据环境中的 #DEFINE 语句来换用其它类型的数据源。
总结
*****
我们对新的 CursorAdapter 基础类的研究就到这里了。我个人对 CursorAdapter 的出现感到非常的兴奋,并计划给我的应用程序框架中的数据处理部件升升级以更充分的利用它的优点。
VFP CursorAdapter 起步三(作者:Doug Hennig 译者:fbilo)的更多相关文章
- VFP CursorAdapter 起步二(作者:Doug Hennig 译者:fbilo)
用 CursorAdapter 来取得和更新数据 在 VFP8 中新增的 CursorAdapter 基类提供一个统一.易用的数据接口.Doug Hennig 在这个月的文章中演示了怎样使用 Curs ...
- VFP CursorAdapter 起步一(作者:Doug Hennig 译者:fbilo)
CursorAdapter 类是 VFP 8 中最重要的新功能之一,因为它提供了一种简单易用.接口统一的访问远程数据源方式.在这个月的文章里,Dung Hennig 将向你展示 CursorAdapt ...
- Doug Hennig的自定义 DataEnvironment 和 CursorAdapter 类文件 -- SFDataClasses
Doug Hennig的自定义 DataEnvironment 和 CursorAdapter 类文件 -- SFDataClasses.vcx,其中包括:SFCursorAdapter 和 SFDa ...
- GoF设计模式三作者15年后再谈模式
Erich Gamma, Richard Helm, 和 Ralph Johnson在GoF设计模式发表15年以后,再谈模式,另外一位作者,也是四色原型的发明者Peter已经过世. 提问者:如今有85 ...
- VFP 的 CursorAdapter 相关
VFP 的 CursorAdapter 是在VFP 8 中增加的最重要的新功能,它提供了一种采用统一接口的方式来访问远程数据源. 现在正值新冠肺炎期间,闲着也是闲着,在整理原理的资料时,发现十多年前的 ...
- VFP的数据策略:高级篇
VFP的数据策略:高级篇 引语 在“VFP中的数据策略:基础篇”一文中,我们研究了VFP应用程序中访问非VFP数据(如SQL Server)的不同机制:远程视图.SQL Passthrough.ADO ...
- VFP的数据策略:基础篇
VFP的数据策略:基础篇 概述 在VFP应用程序中,有很多方法可以访问非VFP数据(如SQL Server):远程视图.SQ LPassthrough.ADO.XML……本文件将审查不同机制的利弊,并 ...
- vue.js 的起步
vue.js 的起步 转载 作者:伯乐在线专栏作者 - 1000copy 点击 → 了解如何加入专栏作者 如需转载,发送「转载」二字查看说明 介绍 vue.js 是一个客户端js库,可以用来开发单页应 ...
- 三种系统监控工具对比:top vs Htop vs Glances
首先启用 EPEL Repository: yum -y install epel-release 启用 EPEL Repository 後, 可以用 yum 直接安裝 Htop: yum -y in ...
随机推荐
- linux 反选删除文件
一.背景 历史原因自动部署程序的历史版本没有自动删除脚本.导致服务器没有空间了.但是又不能将所有的备份都删除. 所以要求只保留一个备份版本,把其他的删除. 二. 要求 要求:删除 除了 2017110 ...
- vue学习笔记3: 动态绑定
一.知识点 动态绑定: vue-class: 三目写法 对象写法 数组写法 vue-style: 三目写法 对象写法 数组写法 二.代码示例 1. vue-class vue-class三目写法 &l ...
- 用K-Means聚类分析做客户分群
聚类指的是把集合,分组成多个类,每个类中的对象都是彼此相似的.K-means是聚类中最常用的方法之一,它是基于点与点距离的相似度来计算最佳类别归属. 在使用该方法前,要注意(1)对数据异常值的处理:( ...
- 让现有vue前端项目快速支持多语言 - 用.net core程序快速替换中文为资源Key,咱不干体力活
前言 最近应公司上层要求,需要将现有项目尽快支持多语言,而中文内容可以找专业人员翻译.那么咱们说干就干,首先我们项目的前端是用vue写的spa程序且组件方面用的element ui,那么自然而然想到用 ...
- 用tensorflow的Eager执行模式
一.即时执行模式 import tensorflow as tfimport tensorflow.contrib.eager as tfetfe.enable_eager_execution() a ...
- 月经贴 】 Csharp in depth
让你 真正 喜欢 上 C# 编译器 准备 为你 上演 的 奇迹. C# 3 中 相对 乏味 的 一些 特性 开始. 自动 实现 的 属性 和 简化 的 初始化, 有一个 私 有的 无 参 构造 函 ...
- 手写 Promise
在上一章节中我们了解了 Promise 的一些易错点,在这一章节中,我们会通过手写一个符合 Promise/A+ 规范的 Promise 来深入理解它,并且手写 Promise 也是一道大厂常考题,在 ...
- Shell常用命令之ip
前言 linux的ip命令和ifconfig类似,但前者功能更强大,并旨在取代后者.使用ip命令,只需一个命令,你就能很轻松地执行一些网络管理任务.ifconfig是net-tools中已被废弃使用的 ...
- shiro 基础使用
引 言 相关内容 : https://blog.csdn.net/superyayaya/article/details/94408805 在web 中, 不同角色的用户, 具有不同的访问权限, 有的 ...
- 【转】spring framework 5以前体系结构及内部各模块jar之间的maven依赖关系
作者:凌承一 出处:http://www.cnblogs.com/ywlaker/ 很多人都在用spring开发java项目,但是配置maven依赖的时候并不能明确要配置哪些spring的jar, ...