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!

Queryunload

Status
Not open for further replies.

AlastairP

Technical User
Feb 8, 2011
286
AU
Good morning,
I have an object reference which I need to release, and would like to have this code fire perhaps in queryunload

However this event does not fire when "Thisform.release" is used in a command button to exit form.

I want to avoid having to go through the application and adding code to every form command button where it is released.
Unload method is too late as the form has already hung trying to release objects.

Is there a way to force the Queryunload event to fire in my form class?
 
What about putting it in the Destroy event? That fires after QueryUnload but before Unload.

The important point is that, in Destroy, all the form's contained objects, properties, methods, etc. are intact. That's not the case in Unload.

You're right that QueryUnload doesn't fire in response to the form's Release method (nor if you release the form object).

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
No, in this case, I even guess BINDEVENT isn't possible, as it's having some exceptions and preconditions for some methods and events.

It's a good exercise to have base classes so you can have a central code that acts in all forms.

And the native events have some downsides, as you noticed already. Release() is meant to be very enforced, but indeed most apps will check for unsaved changes before allowing to close, there also is the form.Closeable property and in QueryUnload you want to apply different strategies depending on what is the source of the query for unloading the form.RelaseType

There is a good way to wire all this, and that needs the OOP way of inheriting this from a base class, not a hack with bindevents, I would recommend going the hard route even if bindevent would work.

Both the QueryUnload event and the Release method should call a user-defined method to check whether you want to allow to close the form, in QueryUnload you then would differ from Release if the ReleaseType is 2, which means a shutdown. Then you might disregard unsaved changes and at best would TableREVERT() them, while the Release() method in such a case might focus the save button and ask to save or cancel changes via a messgebox().

Both QueryUnload and RElease can be canceled with NODEFAULT. If closing is allowed, both should set the form.closable=.T.
You will have some common code in Release() and QueryUnload(), but that's OK, you need different overall strategies in the even QueryUnload and the method Release(). Programming code in the Release() will cover all places calling that method for triggering the native base behavior of releasing the form, your new code in that method can stop any call in the one central place.

These are two exits, the common code they call is for determining whether the situation allows closing, you might add another user-defined method to execute at the stage in both QueryUnload and Release, when you're deciding to close the form even forcefully and let that already do part of the cleanup that doesn't work naturally, but there is something wrong, if some references deadlock each other.

Bye, Olaf.


Olaf Doschke Software Engineering
 
Hi Mike,
I will try putting my code in the form destroy event. However I did try putting the code in the destroy event of the object to be released - but that did not seem to fire, hence did not try it in the form method.
I will try this later when I get back to my dev computer.

Just to clarify, I have a collection in a form that contains object references to a specific class on any of the other open forms. When I call "thisform.release" from an open form, the collection still holds the object reference to that form. I have a piece of code to remove the item in the collection which I would like to run as the form releases.
The forms are classed.
Just checking my approach is in the right direction?

Alastair
 
Hi Mike,

Destroy event in the form class worked perfectly thanks!

Alastair
 
AlastairP,
I have extended my baseform with following properties: (adapted from and inspired by Andy Kramek)
1) OK2Destroy
Code:
Lparameters tcTable

Local lcTable As String, ;
	llRet As Boolean, ;
	lnAnswer As Number, ;
	lnBuffMode As Number, ;
	lnParam As Number, ;
	lnTable As Number, ;
	lnTablesUsed As Number
Local Array aTablesUsed[1]

If Empty(m.tcTable)
	lcTable = Alias()
Else
	lcTable = m.tcTable
Endif



With Thisform

	llRet =  .Check4Changes(m.lcTable)

	If m.llRet = .F.  &&found uncommitted changes
		lcMessageText = "Save changements"
		lcMessageTitle = "Take care"
		lnDialogType = getmessagebox(4+16)
		lnAnswer = Messagebox(lcMessageText  , m.lnDialogType,lcMessageTitle ,  0 )  &&  Yes = 6, No = 7, Cancel = 2
		Do Case
			Case m.lnAnswer = 6
				lnBuffMode = CursorGetProp( 'Buffering', m.lcTable )
				If  m.lnBuffMode <> 5
					=CursorSetProp("Buffering",5,m.lcTable)
				Endif
				.AllUpdated = Tableupdate(.T.)
				If Type(' .AllUpdated')='N'
					.AllUpdated = Iif( .AllUpdated=0,.F.,.T.)
				Endif
			Case m.lnAnswer = 7
				*!* Created by Koen Piller
				*!* 26-9-2015 11:04:42
				*!* Purpose : buffferingcheck - to avoid "function requires Buffering - in development
				lnBuffMode = CursorGetProp( 'Buffering', m.lcTable )
				*** If  buffering, just  .T.
				If  m.lnBuffMode = 5
					llRet = .T.
				Else
					=CursorSetProp("Buffering",5,m.lcTable)
				Endif
				*!* end bufferingcheck
				.AllUpdated = Tablerevert(.T.)
				If Type(' .AllUpdated')='N'
					.AllUpdated = Iif( .AllUpdated=0,.F.,.T.)
				Endif
			Case m.lnAnswer = 2
				llRet = .F.
		Endcase

		llRet = .AllUpdated
	Else  &&&&found uncommitted changes
		lnTablesUsed = Aused(aTablesUsed,1)
		If Thisform.Killcursor = .T.
			For lnTable = 1 To m.lnTablesUsed
				lcTable = m.aTablesUsed(m.lnTable,1)
				If iscursor(m.lcTable)
					** Then close cursor
					Try
						Use In (m.lcTable)
					Endtry
				Endif
			Endfor
		Endif
	Endif
Endwith

Return m.llRet

and
2)Check4Changes
Code:
Lparameters tcTable

Local Array aTablesUsed[1]

Local lcFldState As String, ;
	lcTable As String, ;
	llRetVal As Boolean, ;
	lnBuffMode As Number, ;
	lnRec As Number, ;
	lnTable As Number, ;
	lnTablesUsed As Number
 
lnRec = 0

lcTable = Iif(Vartype(m.tcTable) # 'C' Or Empty(m.tcTable), Alias(), Alltrim(Upper(m.tcTable)))
If Empty( m.lcTable) Or ! Used(Juststem( m.lcTable))
	lcTable = Thisform.Masteralias
Endif
lnBuffMode = CursorGetProp('Buffering', m.lcTable)

If lnBuffMode = 1
	Return .F.
Endif

lnTablesUsed = Aused(m.aTablesUsed)
For m.lnTable = 1 To m.lnTablesUsed

	If CursorGetProp('sourcetype',aTablesUsed[m.lnTable,1]) = 3	Or CursorGetProp('sourcetype',aTablesUsed[m.lnTable,1])>100	&&skip for views
		CursorSetProp('Buffering',m.lnBuffMode,aTablesUsed[m.lnTable,1])	&&optimistic table buffering
	Endif

Endfor

If Inlist( m.lnBuffMode, 2,3)
	lcFldState = Nvl( Getfldstate( -1, m.lcTable), '')
	llRetVal = !Empty( Chrtran( m.lcFldState , '1', ''))
Else
	lnRec = Getnextmodified(0,m.lcTable)
	llRetVal = !(Getnextmodified(0,m.lcTable) # 0)
Endif

Return m.llRetVal

I have than in my Release method:
Code:
Local llRet As Boolean
llRet = .F.
If Not Thisform.OK2Destroy(m.llRet)
	Nodefault
Endif
and in QueryUnload:
Code:
Thisform.Check4changes()
If Not Thisform.OK2Destroy()
	Nodefault
Endif

It works fine (for me) - if anyone has some observations/improvements please donot hesitate

Regards,

Koen
 
Hi Koen,

I've got something roughly similar in my base DataForm class. A DataForm is any form that allows the user to view / edit / add / delete data from one or more tables. The QueryUnload calls a method called OKToClose, which works similarly to yours.

One minor improvement I have is in the wording of the user message. I have a form property called cFriendlyName, which always contains the name of the entity being edited, as known to the user. For example, if the form edits data from the Customer table, the cFriendlyName woudl simply be "customer" (in lower case, in the singular). My user message would then be:

Code:
lnReply = MESSAGEBOX("Do you want to save your changes to this " + thisform.cFriendlyName + "?", ;
	3+48, gcShortTitle)

As I said, it's a very minor point, and does not affect the overall logic. But I do believe in paying close attention to the wording of all user-visible messages, and anything you can do to make the messages even a tiny bit more informative is always worthwhile.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
AlastairP said:
When I call "thisform.release" from an open form, the collection still holds the object reference to that form.
That comes unexpected, as collection have the fine feature of automatically removing items referencing objects, the moment they are released:

VFP help on the form.release method said:
When you have an object reference to a form in a collection, and you call the form's Release method, the object is removed from the collection without having to release or remove the form from the collection first.

And also vice versa, releasing the collection releases the objects referenced in it:
VFP help on the collection class said:
When Visual FoxPro releases a collection, which contains object references, it also releases any objects in the collection if they are not referenced elsewhere. Make sure to release objects and any references to those objects from a collection before releasing the collection itself from memory.

And it is the way the help describes this, undoubted and used in this away by me many times.

So what do you actually store in the collection? The form reference or just its name or some other related value? You might make it harder on yourself than it actually is. This also is no subject of any bug, so this works since the collection object was added to the VFP base classes in (I think) VFP 8. What's your code to generate the collection? Did you subclass it and changed methods/events? Do you really use a collection object or an array? And last not least, do you store references elsewhere, too? That might hinder the release, if you overcomplicate things. An object only releases once all references to it (outside collections) are removed. References in collections are good to keep forms from automatically releasing.

This all works on the basis of reference counts and what you might be doing wrong is first creating a form variable reference in a private or public variable you don't release after storing the reference to the collection. In the simplest manner each form can add itself to a general forms collection in its load via goFormCollection.Add(THISFORM,THISFORM.Class)

You might use another key, eg TRANSFORM(THISFORM.HWND) will be something unique for each form, but won't let you easiyl find a form of some class, you will need a unique key, if you want to allow multiple forms of the same class, especially many baseclass forms. You might delegate this task to add the form to the collection to a form handler creating the form instead of letting each form register itself, there are always tons of ways. Another thing that might interfere with teh form release is starting a form LINKED to a varname, which is the other variant of varname=createobject(Formclass). Anyway, you will have problems when you mix strategies and partly use DO FORM, partly CREATEOBJECT(), partly even DEFINE WINDOW. Consolidate on one method of handling forms and then all this will work out nicer, most likely.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Hi Olaf,
Thanks for the tips.
I have used collections before successfully and never had trouble releasing object references.
I will look into your suggestions and see what I can find.

Alastair
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top