Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

DataEnvironment and Cursor objects in code

Status
Not open for further replies.

vfp4ever

Programmer
Oct 18, 2013
71
East Java
Hello,

I have defined a simple DataEnvironment class in code "example.prg", containing a couple of Cursor objects and a Relation:

Code:
DEFINE CLASS dte AS DataEnvironment
[indent]ADD OBJECT cur_1 AS Cursor WITH Alias = "tbl_1", CursorSource = This.cur_1.Alias, Database = "dbc\database.dbc"[/indent]
[indent]ADD OBJECT cur_2 AS Cursor WITH Alias = "tbl_2", CursorSource = This.cur_2.Alias, Database = "dbc\database.dbc"[/indent]
[indent]ADD OBJECT rel AS Relation WITH ParentAlias = "tbl_1", ChildAlias = "tbl_2", ChildOrder = "tag_1", RelationalExpr = "field_1"[/indent]
ENDDEFINE

In its Init( ) event code I call .OpenTables( ) and set the .InitialSelectedAlias to .cur_1.Alias, while in the Destroy( ) event code I call .CloseTables( ). I added in my form definition DEClassLibrary = "example.prg" and set DEClass = "dte": all works as expected, as I can see on a Grid.

Now I would like to see tbl_2 records in a different order. If I try to set the .Order property of the child table using Order = "tag_2", it is completely ignored (as stated under Order property in the Help file). How can I display records on my Grid in a different order (tbl_2 has several more tags than the one used in RelationalExpr), using tbe above method of Cursor and Relation objects?

Also, I do not seem to be able to address the DataEnvironment instance in my form methods, it seems as there is no such object contained in it (unlike what I see under DataEnvironment property in the Help file).
 
I believe that your problem is in the relationship between tbl_1 and tbl_2. tbl_1 is the parent and tbl_2 is the child which must be in order of the key in the RelationalExpr. Changing the order of tbl_2 to tag_2 changes the relationship set with ChildOrder = "tag_1"; hence, no records visible.
 
Greg has that right, you can't have both the order you wish and a relationship based on another tag, so either define a tag that achieves both relating and ordering by an expression on two fields or chose one over the other.

There are other ways than a set relation to limit data in one workarea to children, too, you can use SET KEY. Also, you'd need to make use of afterrowcolchange event, for example, to change the SET KEY for another parent key.

Again that needs an index tag, though, which isn't available for ordering then. So a compound index, which VFP can do via an index expression like transform(fld1)+transform(fld2) or something more natural transforming both fields to fixed length strings that combine to a most/least significant ordering. The relationalexpr has to be by the most significant part, though, and you have to SET EXACT OFF/ SET NEAR ON. So you see it's complex and the options are limited.

In regard of the DE as an object, yes, that was never different in an SCX, now you have these memberclass properties in form classes, you can just define what DE to use, but you don't have means on addressing the DE as a subobject of Thisform. You can't call into DE methods from form methods. What's mostly missing is to be able to call a Cursoradapter CursorFill() method for a CA in the DE.

How often do I say I turned back on the DE, and even now it's a bit hard to get to use it. I've recently said I used a framework using DE classes as basis of data access/business objects, this wasn't using DE classes added to the form level, though, IIRC, just instancing them as child object? I actually have no hands on it anymore.

But simple experiment, you can put aside the memberclass properties of a form and simply add a DE object as child, the form is a valid container for a DE.

Before you go that route, I hope someone else knows better, as I faintly remember having had a lesson from someone showing me how to address an SCX DE from the form. It somehow is possible, I think, and going the official route you get the extra bonus of some automatisms. Then I guess some of that depends on a DE being defined at design time, maybe latest at Load, though as far as I know the DE loads even earlier, ie the Form.Load can already address DBFs you loaded in the DE.

Last not least, I think you give away one easier to handle design advantage by writing PRG Classes. Clearly DE isn't about GUI, but getting to all the detail properties by context menu of the visual DE cursors and the relation lines drawn in and seeing the table field lists and index tags, all that was at least a very useful feature of the DE.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Ah, short experiment later:

When I add a DEclass and DeClassLibrary to a form and create it, the class name becomes the object name. So Thisform.dte should exist in your case.
And furthermore, whatever you define as Name property of the DE class will be used as object name for the form:

Code:
o = Createobject("myform")
? o.DatEnv.Class
? o.DatEnv.ClassLibrary

Define Class myform As Form
   DEClass = "myde"
   DEClassLibrary = Sys(16)
Enddefine

Define Class myde As DataEnvironment
   Name = "DatEnv"
Enddefine

Now this can become confusing, not only is the DE object name taken from the name you set in your class definition as property (which can differ from the class name itself), the class property of the object also tells the name, not really the class this DE is based on. Ah, those VFP quirks...

You could set the name of all your DE classes to the same and always have a standard name for all DE objects, that's maybe the best you can make from this to not need things like macrosubstitution for addressing the DE object. A given name obviously is also necessary, because otherwise your problem of getting the object reference just shifts to getting at the name property of an object for which you have no reference.

The DE has the same problem of not being able to address the form it belongs to with Thisform, by the way.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Ouch, I thought the solution was as simple as the example I exposed! So far, for similar purposes, I have always used basic USE, SET RELATION TO and SET ORDER TO statements with no special fuss, and without the need to use SET KEY or SET SKIP additions either. I always wondered what these Cursor, Relation objects were for, hence my curiosity (and disappointment, if their use is so limited). After all, who wants to relate a customer table and see their single orders in "natural" order? Anybody would want to see the orders listed after date, or country, or whatever other field is part of the child table. Sure, the RelationalExpr must be customer_id, it can't be BINTOC( customer_id ) + DTOC( order_date ), can it? (order_date is only on the child table)

On the other hand, I can still issue a SET ORDER TO <some compund key> (on tab_2) and have my list sorted out as I want (as seen with DEBUGOUT), so I don't see why it cannot be done simply with the Order property.

Olaf, assigning a specific name to the DE does not help to make its reference "visible". That is, even if I define the DE with Name = "dte_Name", I cannot see "dte_Name", "DataEnvironment" or anything related to it other than DEClass and DEClassLibrary among the form properties. Indeed, it appears as if the DE is created before the Form.Load event (but after the FormSet.Load event).
 
Ah, you're using a formset? I don't know how that works there, but the de object reliably appeared on a single form class as in my code example, just try it.
You should be able to index on BINTOC( customer_id ) + DTOC( order_date )

Overall, I, mean, just to state the obvious, the rest of the world uses SQL, joins, to query data, there you don't set order to an index.
You might find it on the formset level, formsets are evil, though... but that's another topic.


The relations needs one table indexed in the way the field or expression you set INTO the other becomes an index lookup, the other table can have an order that's not related to the relation, yes. That may be what you're seeing. It's too late here for me to wind down the details of which table has to have the index set for the relation and which can be freely ordered by any other index. But one thing is for sure: You can't set two orders at the same time. So when it some to the order tag for the table that the relation indirectly seeks in, that can also have an order, just the most significant part needs to match the expression you set into the table. And that's quite restrictive for the ordering, unless the relation is by a single ID anyway, ie all child data of a customer id. Sort by any second field? Yes BINTOC(customer_id)+otherfield or some function of the other field that is type C. Set Near on, set relation to BINTOC(cusomterid) into .... and the relation fins all records where the customer_id matches and the rest of the index expression sorts the details.

Bye, Olaf.


Olaf Doschke Software Engineering
 
Sure Olaf, obviously we do prefer usual ways to do it. Mine is just an experiment to convince myself about the dubious usefulness of Cursor and Relation objects. Have a look here (and play with commented lines):

Code:
CLEAR

CREATE DATABASE a_database

CREATE TABLE a_parent( ;
	parent_id I, PRIMARY KEY parent_id TAG parent_id, ;
	country C( 30 ), UNIQUE country TAG country )
SET ORDER TO TAG parent_id	&&	prepare for relationship

CREATE TABLE a_child( ;
	child_id I, PRIMARY KEY child_id TAG child_id, ;
	parent_id I, ;
	population I, UNIQUE population TAG population, ;
	city C( 30 ), UNIQUE city TAG city )
INDEX ON parent_id TAG parent_id
SET ORDER TO TAG parent_id	&&	prepare for relationship

SELECT a_parent
SET RELATION TO parent_id INTO a_child	&&	apply relationship
SET ORDER TO TAG country	&&	order by country (as preferred)
GOTO TOP	&&	first country in alphabetical order

SELECT a_child
SET ORDER TO TAG population	&&	order by population (as preferred)

* //	not ordered by country (natural order)
INSERT INTO a_parent VALUES( 456, "Germany" )
INSERT INTO a_parent VALUES( 123, "Italy" )
INSERT INTO a_parent VALUES( 345, "Scotland" )
INSERT INTO a_parent VALUES( 234, "United States" )
INSERT INTO a_parent VALUES( 567, "France" )

* //	not ordered by parent_id, population or city (natural order)
INSERT INTO a_child VALUES( 1001, 345, 488050, "Edinburgh" )
INSERT INTO a_child VALUES( 1002, 456, 249023, "Kiel" )
INSERT INTO a_child VALUES( 1003, 345, 611748, "Glasgow" )
INSERT INTO a_child VALUES( 1004, 456, 634830, "Stuttgart" )
INSERT INTO a_child VALUES( 1005, 123,  48578, "Ascoli Piceno" )
INSERT INTO a_child VALUES( 1006, 567, 479553, "Toulouse" )
INSERT INTO a_child VALUES( 1007, 345,  70000, "Inverness" )
INSERT INTO a_child VALUES( 1008, 567, 232787, "Lille" )
INSERT INTO a_child VALUES( 1009, 456, 570000, "Bremen" )
INSERT INTO a_child VALUES( 1010, 123, 122000, "Bergamo" )
INSERT INTO a_child VALUES( 1011, 345, 198880, "Aberdeen" )
INSERT INTO a_child VALUES( 1012, 456, 535061, "Hannover" )
INSERT INTO a_child VALUES( 1013, 234, 428000, "Fresno" )
INSERT INTO a_child VALUES( 1014, 345,  37810, "Stirling" )
INSERT INTO a_child VALUES( 1015, 123, 583601, "Genova" )
INSERT INTO a_child VALUES( 1016, 345,  47180, "Perth" )
INSERT INTO a_child VALUES( 1017, 456, 289544, "Wiesbaden" )

LOCAL loForm, loFormSet AS Object
loForm	= CreateObject( "a_form" )
loForm.Show( 1 )

*loFormSet	= CreateObject( "a_formset" )
*loFormSet.TFrm.Show( 1 )

CLOSE DATABASES ALL
CLOSE TABLES ALL
DELETE DATABASE a_database
CLEAR ALL

*----------
DEFINE CLASS a_dataenvironment AS DataEnvironment
	AutoCloseTables	= .F.
	AutoOpenTables	= .F.

	ADD OBJECT a_parent AS Cursor WITH Alias = "a_parent", CursorSource = "a_parent", Database = "a_database.dbc", ReadOnly = .T.
	ADD OBJECT a_child AS Cursor WITH Alias = "a_child", CursorSource = "a_child", Database = "a_database.dbc", ReadOnly = .T., Filter = "a_child.parent_id == a_parent.parent_id"
	ADD OBJECT a_relation AS Relation WITH ParentAlias = "a_parent", ChildAlias = "a_child", ChildOrder = "parent_id", RelationalExpr = "parent_id"

	PROCEDURE Destroy( )
		This.CloseTables( )
	ENDPROC

	FUNCTION Init( ) AS Boolean
		This.OpenTables( )

		RETURN .T.
	ENDFUNC
ENDDEFINE

*----------
DEFINE CLASS a_form AS Form
	DECLassLibrary	= SYS( 16 )
*	DEClass	= "a_dataenvironment"
	Height	= 480
	Left	= 100
	Top	= 100
	Width	= 640

	ADD OBJECT a_textbox AS TextBox WITH Left = 10, Top = 20, Width = 100, ControlSource = "a_parent.country", DisabledBackColor = 0xFFFFFF, DisabledForeColor = 0x000000, Enabled = .F.
	ADD OBJECT a_buttondn AS CommandButton WITH Left = 150, Top = 20, Width = 23, Height = 22, Caption = "<"
	ADD OBJECT a_buttonup AS CommandButton WITH Left = 180, Top = 20, Width = 23, Height = 22, Caption = ">"
	ADD OBJECT a_grid AS Grid WITH Left = 10, Top = 100, Width = 620, Height = 360, RecordSourceType = 1, RecordSource = "a_child", AllowCellSelection = .F., ReadOnly = .T.

	FUNCTION Init( ) AS Boolean
		* //	I can see a_form.a_dataenvironment only when a_form does not belong to a_formset
		IF !PEMSTATUS( This, "a_dataenvironment", 5 )
			SET ORDER TO TAG population IN a_child	&&	--- problem: CANNOT CHANGE ORDER if a_dataenvironment is defined ---
			SET FILTER TO a_parent.parent_id == a_child.parent_id	&&	at least I can alternatively set a_child.Filter
		ENDIF

		This.Refresh( )
		This.a_grid.DoScroll( 2 )

		RETURN .T.
	ENDFUNC

	PROCEDURE a_buttondn.Click( )
		WITH This.Parent
			SKIP -1 IN a_parent

			IF BOF( "a_parent" )
				GOTO TOP IN a_parent
			ELSE
				.Refresh( )
				.a_grid.DoScroll( 2 )
			ENDIF
		ENDWITH
	ENDPROC

	PROCEDURE a_buttonup.Click( )
		WITH This.Parent
			SKIP 1 IN a_parent

			IF EOF( "a_parent" )
				GOTO BOTTOM IN a_parent
			ENDIF

			.Refresh( )
			.a_grid.DoScroll( 2 )
		ENDWITH
	ENDPROC
ENDDEFINE

*----------
DEFINE CLASS a_formset AS FormSet
	Name	= "a_formset"
	WindowType	= 1

	ADD OBJECT TFrm AS a_form
ENDDEFINE

You were right, the DataEnvironment does not show in the Form properties if the Form belongs to a FormSet. The question remains the same: why should I want (in this simple case of showing a form with a parent-child relationship) to use my own DataEnvironment with contained Cursor and Relation objects if I cannot get the same results?
 
First of all, answering here before I rtry your code.

Obviously DE classes can establish a class hierarchy pf DEs that offer an OOP way of building up set of data you need for corresponding giearchy/family of forms.

So the DE doesn't show on the form level, so what? I think it'll show on the formset level, that's what every form sees, too. So just look at that level and I guess you find it. IIRC from the shot experiences I made with formsets they are a) only available in SCXes and b) they establish the data session used by all forms, so why should the DE be at form level when you use a formset? There only is one for the formset and it will be established there.

See, if you actually can't cope with a shared datasession, then don't use formsets. You can do everything formsets can do in a shared datasession, that is the main form of your virtual set (no SCX formset created, just single forms or formsets) establishes the private datasession the forms share and creates the other forms that join the same private datasession by not establishing one themselves. That way you do have formsets without using a formset and can use DEs each form has. Just notice the shared data session (not to be confused by data environment) means the two or more DEs of the forms will not only have their own tables and views and CAs, they share a session into which their tables etc are opened and there can be clashes, if you design that badly.

But you can also logically join forms that each have their own private datasession and DE not intefering with each other. So doing formsets on your own gives you the ability to pick from more options.

OK, now I'll run your code and see what it tells me. I see you point out a problematic line in your comments. Let's see...

Bye, Olaf.

Olaf Doschke Software Engineering
 
Your problem isn't about the DE class, it's about the relation only.

You can't order by something that hasn't the parent_id first so the relationship is kept intact.

And of course, you can't just add an index, you also have to adapt the relation its3elf. It can't just be based on an integer, as you can only do a combined index on Bintoc(id)+rest. So the relation itself has to be changed, too, the relationalexpr has to change from paren_id to bintoc(parent_id), the partial index expression about the parent id only.

Your problem only has to do with the RELATION, no more, no less.

As the relation requires an index in which the relationalexpr can bee seeked in, you can't just set order to anything not having the parentid in it. But you can establish an index with parent id and then the column you want to sort by as for a country (parent) you pick, the children will all have the same parent id, so their sub ordering becomes their main ordering.


Code:
* an index to add to the a_child table:
Index On BinToC(parent_id)+BinToC(population) Tag prnt_popl

* a necessary change in the relation of the DE:
ADD OBJECT a_relation AS Relation WITH ParentAlias = "a_parent", ChildAlias = "a_child", ChildOrder = "[highlight #FCE94F]prnt_popl[/highlight]", RelationalExpr = "[highlight #FCE94F]BinToc(parent_id)[/highlight]"

That's a restriction from the relation, not from the DE. The relation within your DE restricts you from ordering the child workarea any other way that breaks seeking the parent_id from the a_parent table in a_child in an index that isn't about the parent_id.

So you need index tags that have the parent_id first! That's the only restriction you have to deal with, and that always was there, also as there wasn't a DE class. Now establish indexes on both BinToC(parent_id)+BinToC(population) and BinToC(parent_id)+BinToC(city) and your sorting in the grid can by be population or by city. It won't break the relation, when you switch between such indexes, as the relationalexpr = "BinToC(parent_id)" stays put and finds its records in a_child.

And if you don't want that complex indexing, then don't make use of the relation. The problem is only with it, not with the DE itself.

Bye, Olaf.

Olaf Doschke Software Engineering
 
And by the way, there was a thread in the past, I don't know if that thread was with you or someone else. I already established that you can easily shoot yourself in the foot using both DE relation objects and grid properties and own SET RELATION commands contradicting each other or establishing some relations with set skip, some not, or establishing mutual relations.

All that world of contradicins is by relations, not by DE. And SQL avoids it in defining relationships by the joins you apply.

But it has absolutely nothing to do with DE, a relation you establish in the same way in form load also breaks when the parent_id is looked up in the population index. It doesn't crash with an error, as both columns are integer. But of course once you establish a relational expression you lookup any other index you want to set has to start with the parent_id of the child table so the parent table finds its child records.

What you're doing is changing the join condition from first matching a_parent.parent_id with a_child.parent_id and then matching a_parent.parent_id with a_child.population
Of course, you suddenly see no cities anymore, no city has a population that corresponds to a parent_id, a country.

There is no easy peasy in this with neither xbase relations nor SQL, the SQL makes one thing easy:eek:nce you have your few cities from the country you can take that result and index it by its single columns and order by it. What's not automatic noew is that these child data updates itself whenever you change to another parent. So navigation in the parent has to requery child data. But this way you do have independency as the query of child cities is then done independently from ordering them. It's also only done at that time once, a relation is reevaluated every time you move in the parent. And that again is it's advantage. The relation also acts as a filter. when you would display the paren_id via some country combobox as dropdown lost to pick from and change the city to another country, it'll vanish from the grid and the relation adapts data to what's currently related. Updating a curosr/view country it'll stay put in the list until you save and then requrey and don't match it anymore.

And the latter is something I actually also see as an advantage of SQL joins over relations, as nice as it is, that a relation automatically adjust what actually is related. In the session I edit something and actually remove it from the parent that way, I may want to still doo other changes and not need to be cautious about making the change that lets the child leave its parent last, so I can make other changes without first changing the parent to the newly picked country.

Now pick your difficulty, either index by as many expressions you need to satisfy any relation you want to make and any sub ordering of data within he group of same parent_id or move from relations and go over to views fetching data at first and enabling you to index them as aftermath to then have simple indexes to sort the child group by, And both have advantages and disadvantages. You can't cherry pick just the advantages.

Bye, Olaf.

Olaf Doschke Software Engineering
 
OK, Olaf.

I am not here to discuss whether it's good or not using FormSets (I may believe they create more problems than benefits), just wanted to check if a_dataenvironment shows up in the properties or is hidden. By the way, surely it would not be a member of the form.. yet I cannot see it within the formset! PEMSTATUS( This, "a_dataenvironment", 5 ) returns false in a_formset.Init( ).

Going OOP is the main reason why I would want to substitute archaic USE, SET RELATION, SET ORDER statements that remind me of dBaseIII Plus. That is why I am now testing DataEnvironment, Cursor and Relation objects. I am well aware of the benefits of SQL views, although I reckon it would be too much for a simple example of a countries/cities form. Just to point it out once more: it does work as I expect it with USE and SET commands (I can choose whatever order I want in both a_parent and a_child, once the relationship is established), I just do not undestand how it "translates" with the new OOPish DE/Cursor/Relation objects.

Back to code: I have changed the "INDEX ON parent_id TAG parent_id" after creating a_child with your "INDEX ON BINTOC( parent_id ) + BINTOC( population ) TAG pid_pop", followed by "SET ORDER TO TAG pid_pop". So now the relation is established with "SET RELATION TO BINTOC( parent_id ) INTO a_child", right? Well, without using a_dataenvironment it works exactly as before: we are still using dBaseIII Plus instructions. So now I remove the asterisk and let a_dataenvironment be created, updating the Relation object with ChildOrder = "pid_pop", RelationalExpr = "BINTOC( parent_id )". Now the grid remains empty. I must have missed something.
 
Well, you want the DE to take the responsibilty, don't you, so you have first to comment out the section establishing the relation in code:

Code:
*   SELECT a_parent
*   SET RELATION TO BinToC(parent_id) INTO a_child   &&   apply relationship
*   SET ORDER TO TAG country   &&   order by country (as preferred)
*   GOTO TOP   &&   first country in alphabetical order

*   SELECT a_child
*   SET ORDER TO TAG population   &&   order by population (as preferred)

When a DE starts in a datasession already populated with the tables the DE should open and maintain, then it can't do it's job.
You're also misinterpreting the object to be worakreas of the relation itself. They are definitions applied at DE start, what is then established is the workareas and relations. changing the objects doesn't do anything. To change order of a workarea you still need to SET ORDER

The DE only is about the initilization, not giving you objects at hand that represent the workarea. The cursoradapters you can also use in the DE are perhaps closest to that, as they are not just having a few properties but also some events and methods. So DE is mainly for the initialization and deinitialisation of workareas and your code will still mainly act on them and not on the DE cursor objects.

The way I told you to set up the a_relation object will work, supposed a_child isn't already open with order set to population like your initial code does. That should be clear. The a_relation also isn't an object representing the relation through its lifetime of the DE and/or datasession lifetime (closing that obviously the object would become a zombie not maintaining a relation as the whole session is gone).

Encapsulation of workareas as objects is still something you design separately or, if you really want to put into DE derived class methods. Obviously you can, but then the code you write will not just be for one table but for the set of data maintained in the DE. This can be a fine concept too. The usual approach is to have a class family for a certain table or perhaps table family (like persons,customers, employes all inherit some person properties and behavior and methods, for example).

Your base class for that is rather the cusrosradapter than the DE and the cursor objects in the DE have no base class you can inherit from and and subclass.

Regarding getting at that object with your code becomes quite unimportat, doesn't it? You give too much importance to the cursor objects of the DE. They are merely a designtime blueprint of what should happen at form start, no more no less. The relation can be inset without needing to release the a_relation object from the DE and vice versaa, when you would release the a_relation object that wouldn't unset the relation.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Just by the way, even though I think you have to reconsider what you want to do with an oop approach of VFP data access with DE classes, your code yields access to the a_dataenvironment object.
a_datasession_vygz4i.png


And intellisense sees the objects in the DE:
a_datasession_intellisense_i9zuaq.png


Code:
? PemStatus(Thisform,"a_dataenvironment",5)
yields .T. for me, too. I don't know what's wrong with your VFP.

But, for example Thisform.a_dataenvironmane.a_child.order is empty, whereas set('order') is telling about the tag used. So yes, this isn't a live representation of the workareas. If it was, it would need to be restricted in some ways anyway as, for example, you can't rename a workarea you can only use a cursor again alias another name.

I can do thisform.A_dataenvironment.removeobject("A_RELATION") and the object then is gone, but the relation still exists between a_parent and a_child workareas. I SET FILTER TO in a_child and the a_child.filter property of the cursor object still is the expression you set, etc. etc. These objects all are already done when the init has finished, the lose their meaning.

So realize it, the DE is merely an initializer, when the form init runs it's already done, the only other time it becomes active again is when closeing the form and its autoclosing tidies up workareas.

Subclassing it and creating a hierarchy of DE Classes the most you get is subclassing compositions of tables usable for a parallel hierarchy of forms or formsets. So a DE is combinable with forms, not just at the last stage when you finally use vcx forms as the components of a scx formset. or a formset class. The objects within are less bound to

Besides, your init code now doesn't have to do anything. And the main idea of MS//VFPTeam I think was, that for many cases and applications thought of being done in VFP an initial setup of the dataset composition and relations between them is all you need.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Lets look at it from the other side.

In other OOP language you design or use an ORM that gives you objects of the data. Well, in VFP these objects always were the workareas you can address with name.property syntax, so records as plain vanilla object already were a thing in the 80ies.

From another perspective though, you can't write classes and subclass them based on cursor or worakrea or whatever you'd name that class, perhaps simply entity/table. Such classes you may design based on custom or container anything unrelated, and they'd just have a pointer to perhaps both the dbf file, viewname, dbc, then workarea alis aetc. You'll always have to face that these objects never fully encapsulate a cursor, you always have your class code and then some vFP constructs that you handle.

You can turn a small table into a collection, fetch data into arrays or something along that lines, but in the end you have to go back to the woarkarea to write things out to hdd, it's your only connection to the dbf file and always will be. You have to live with the quirks that some virtual objects exists, for example the buffer and the filter and some other cursorproperties are only accessible through a funny cursorgetprop function, to separate these properties from the fields themselves. But there isn't a workarea object available on the VFP level, you'll have to pull everything together in your own class and still the workarea itself will always live independent of that object. You have to adhere to rules you set on yourself to anly address the workareas through your own classes and only they are allowed to use legacy xbase commands, so in the long run these classes you design then can also act on remote dataabses or other things with an interface that fits whatever your oop ideas of data access are.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Great, Olaf

I reckon I understand what lies behind it now, thank you very much for your thorough explanation. There is so much theory and so little example code that, sometimes, I feel as if I am already on the descending side of my learning curve!

By the way, earlier today I could not get the correct results because I realized SET EXACT was ON, preventing the combined relation key from working. Now code works exactly the same way, with or without providing a DataEnvironment class. If the form is contained within a formset it works too, but only when DEClass is commented out:

Code:
CLEAR
SET EXACT OFF

CLOSE DATABASES
IF FILE( "a_database.dbc" )
	DELETE DATABASE a_database
ENDIF
CREATE DATABASE a_database

CREATE TABLE a_parent( ;
	parent_id I, PRIMARY KEY BINTOC( parent_id ) TAG parent_id, ;
	country C( 30 ), UNIQUE country TAG country )
SET ORDER TO TAG country	&&	order by country

CREATE TABLE a_child( ;
	child_id I, PRIMARY KEY BINTOC( child_id ) TAG child_id, ;
	parent_id I, ;
	population I, UNIQUE population TAG population, ;
	city C( 30 ), UNIQUE city TAG city )
INDEX ON BINTOC( parent_id ) + TRANSFORM( population, "999999" ) TAG pid_pop

INSERT INTO a_parent VALUES( 456, "Germany" )
INSERT INTO a_parent VALUES( 123, "Italy" )
INSERT INTO a_parent VALUES( 345, "Scotland" )
INSERT INTO a_parent VALUES( 234, "United States" )
INSERT INTO a_parent VALUES( 567, "France" )

INSERT INTO a_child VALUES( 1001, 345, 488050, "Edinburgh" )
INSERT INTO a_child VALUES( 1002, 456, 249023, "Kiel" )
INSERT INTO a_child VALUES( 1003, 345, 611748, "Glasgow" )
INSERT INTO a_child VALUES( 1004, 456, 634830, "Stuttgart" )
INSERT INTO a_child VALUES( 1005, 123,  48578, "Ascoli Piceno" )
INSERT INTO a_child VALUES( 1006, 567, 479553, "Toulouse" )
INSERT INTO a_child VALUES( 1007, 345,  70000, "Inverness" )
INSERT INTO a_child VALUES( 1008, 567, 232787, "Lille" )
INSERT INTO a_child VALUES( 1009, 456, 570000, "Bremen" )
INSERT INTO a_child VALUES( 1010, 123, 122000, "Bergamo" )
INSERT INTO a_child VALUES( 1011, 345, 198880, "Aberdeen" )
INSERT INTO a_child VALUES( 1012, 456, 535061, "Hannover" )
INSERT INTO a_child VALUES( 1013, 234, 428000, "Fresno" )
INSERT INTO a_child VALUES( 1014, 345,  37810, "Stirling" )
INSERT INTO a_child VALUES( 1015, 123, 583601, "Genova" )
INSERT INTO a_child VALUES( 1016, 234, 115007, "Peoria" )
INSERT INTO a_child VALUES( 1017, 345,  47180, "Perth" )
INSERT INTO a_child VALUES( 1018, 456, 289544, "Wiesbaden" )

LOCAL loForm, loFormSet AS Object
loForm		= CreateObject( "a_form" )
loForm.Show( 1 )

*loFormSet	= CreateObject( "a_formset" )
*loFormSet.a_containedform.Show( 1 )

CLOSE DATABASES ALL
CLOSE TABLES ALL
DELETE DATABASE a_database
CLEAR ALL

*----------
DEFINE CLASS a_dataenvironment AS DataEnvironment
	AutoCloseTables		= .F.
	AutoOpenTables		= .F.

	ADD OBJECT a_parent AS Cursor WITH Alias = "a_parent", CursorSource = "a_parent", Database = "a_database.dbc", ReadOnly = .T.
	ADD OBJECT a_child AS Cursor WITH Alias = "a_child", CursorSource = "a_child", Database = "a_database.dbc", ReadOnly = .T., Filter = "a_child.parent_id == a_parent.parent_id"
	ADD OBJECT a_relation AS Relation WITH ParentAlias = "a_parent", ChildAlias = "a_child", ChildOrder = "pid_pop", RelationalExpr = "BINTOC( parent_id )"


	PROCEDURE Destroy( )
		This.CloseTables( )
	ENDPROC

	FUNCTION Init( ) AS Boolean
		This.OpenTables( )

		RETURN .T.
	ENDFUNC
ENDDEFINE

*----------
DEFINE CLASS a_form AS Form
	DECLassLibrary		= SYS( 16 )
	DEClass			= "a_dataenvironment"
	Height			= 480
	Left			= 100
	Top			= 100
	Width			= 640

	ADD OBJECT a_textbox AS TextBox WITH Left = 10, Top = 20, Width = 100, ControlSource = "a_parent.country", DisabledBackColor = 0xFFFFFF, DisabledForeColor = 0x000000, Enabled = .F.
	ADD OBJECT a_buttondn AS CommandButton WITH Left = 150, Top = 20, Width = 23, Height = 22, Caption = "<"
	ADD OBJECT a_buttonup AS CommandButton WITH Left = 180, Top = 20, Width = 23, Height = 22, Caption = ">"
	ADD OBJECT a_grid AS Grid WITH Left = 10, Top = 100, Width = 620, Height = 360, RecordSourceType = 1, RecordSource = "a_child", AllowCellSelection = .F., ReadOnly = .T.


	FUNCTION Init( ) AS Boolean
		WITH This
			IF !PEMSTATUS( This, "a_dataenvironment", 5 )	&&	if DEClass is commented out
				SET FILTER TO a_parent.parent_id == a_child.parent_id	&&	then I must apply a filter myself
			ELSE
				.Caption	= .Caption + " with its own DataEnvironment"
			ENDIF

			.Refresh( )
			.a_grid.DoScroll( 2 )
		ENDWITH

		RETURN .T.
	ENDFUNC

	PROCEDURE a_buttondn.Click( )
		WITH This.Parent
			SKIP -1 IN a_parent

			IF BOF( "a_parent" )
				GOTO TOP IN a_parent
			ELSE
				.Refresh( )
				.a_grid.DoScroll( 2 )
			ENDIF
		ENDWITH
	ENDPROC

	PROCEDURE a_buttonup.Click( )
		WITH This.Parent
			SKIP 1 IN a_parent

			IF EOF( "a_parent" )
				GOTO BOTTOM IN a_parent
			ENDIF

			.Refresh( )
			.a_grid.DoScroll( 2 )
		ENDWITH
	ENDPROC
ENDDEFINE

*----------
DEFINE CLASS a_formset AS FormSet
	Name		= "a_formset"
	WindowType	= 1

	ADD OBJECT a_containedform AS a_form


	FUNCTION Init( ) AS Boolean
		DO CASE
			CASE PEMSTATUS( This, "a_dataenvironment", 5 )
				MessageBox( "a_dataenvironment is a property of a_formset." )

			CASE !EMPTY( This.a_containedform.DEClass )
				MessageBox( "nobody knows where a_dataenvironment is." )
		ENDCASE

		RETURN .T.
	ENDFUNC
ENDDEFINE
 
OK, that works as you describe, and I don't think the problem with formsets is not solvable.

As far as I see
a) Formsets DO have a DataEnvironment, and it determines the DataEnvironment for the set, the set shares this.
b) Formsets do NOT have DEClass and DEClassLibrary so you can only use the standard DataEnvironment with them.
So you can only make use of the existing DE in a formset.

Again, since VFP developers usually use forms and build their own virtual formsets not a problem, but then also no solution to getting more out of the access to the DEclass, as said it's only having an effect on initialization:

help said:
Note
You can set only the following DataEnvironment properties at run time: DataSource, DataSourceType, InitialSelectedAlias, Name, OpenViews, and Tag. For a new property setting to take effect, you must call the CloseTables and OpenTables methods for the DataEnvironment object. Setting any other DataEnvironment properties at run time generates an error.

So as I said earlier, making changes to properties of the objects in the DE has no effect on the already open workareas. As is said here to let changes in some properties take effect, call into the close and OpenTables method and you end up with what the DE would do, if it had been configured that way at design time already. But that causes all kinds of other troubles in data bound controls. Mainly, but not only in the grid.

Overall, when going back to starting the form, not the formset, what's missing is you're not actually letting the DE do its core job to actually initialize opening the tables, as you simply still have them open from the initialization. So put in a Close Tables All, Close Databases All before starting the form and you'll see the DE in action. Detail problems you had maybe because set exact, yes, but also perhaps because of that, if a table alias is used, the DE doesn't override that, for example. So the form, as you don't set it to use a private datasession, simply takes the table as you left them in the initialization. The DE should open them, set tags and relations, etc. Not your code...

And the next problems with samples preparing their data: The newly created database and tables are open exclusive, no matter what the setting of it is. So close all data to get to the situation a final application would also face, no data opened before starting, the DE initializing the datasession.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Olaf,

I have played a bit with the above code (and minor changes), verifying what you said about the DataEnvironment.

In conclusion, I'd better avoid using it at all, as I would necessarily need a reference to it in order to change relations at runtime (as it's most likely needed in more complex applications). I need to use a FormSet because that is where I create a ToolBar for my main Form. So, in the end, I have to stick to the traditional commands (USE, SET RELATION, SET ORDER, ...) and get along with them. Thank you again for your patience and for shedding some light on the matter.

Regards,
Dario
 
Hello Dario,

okay, that's fine with me. In part you come to the right conclusion for the wrong reason, but it's right anyway.
You still seem reluctant to understand that having access to the relation object of the DE diesn't allow you to change it anyway. I have told that multiple times already.
The DE objects are merely a blueprint plan of what the DE does when a form starts. It's not to be confused with a datasession and it's also not a life interactive objectified view of the workareas of a datasession at all.

So even with object reference to a DE and all its objects, you'd not get on with your idea, as your understanding of the meaning of these objects still is wrong.

So you'd have to use SET RELATION command to make a change to an existing relation anyway And neither will that reflect back into he DE relation object, nor would setting DE relation object properties change a relation. You're barking up the wrong tree anyway.

The DE is just what will be the start situation of the workareas used by a form. It will add to what already is in use in the current datasession if a form works in that and doesn't have a private datasession. Again, DE isn't datasession, those are separate concepts. You can have multiple forms and all in the same datasession, with 3 separate DEs that each just manage the DBFs of their forms especially at the end, if autoclosing closes the dbfs the DE also opened, and that's manbe one of the only aspects that's making life easy when your forms use an overlap and still you don't sabotage other forms auth CLOSE ALL at the end of your form.

The DE is really far less than you imagine it to be.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Just one more side note: relations in themselves are a thing that have quite some inconsistencies as you can define contradicting information in grid properties, relation object properties and relations you code via SET RELATION and SET SKIP and then again the concept of report target alias is not really straight forward to understand.

That's completely unrelated to DEs.

The major advantage apart of defining the sets of tables a form should open at start is that you can drag from the DE into your forms. We didn'T touch that feature at all here, because that's obviously also not possible in a DE defined by PRG code.

Bye, Olaf.



Olaf Doschke Software Engineering
 
The DE is really far less than you imagine it to be.

I'm afraid I sometimes have too much imagination. [dazed]. Yet I'm happy to see everything clear now, thanks to your help.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top