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 Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Error handling - listing properties of a form to an error file 1

Status
Not open for further replies.

AndrewMozley

Programmer
Oct 15, 2005
621
GB
When an error happens in an application, this may have been trapped by :

Code:
ON ERROR DO ErrorRoutine WITH ERROR(), MESSAGE(), PROGRAM(), LINENO() NOCONSOLE

The code which is then executed includes :

Code:
LIST MEMORY LIKE * TO <filename> NOCONSOLE

That works fine: Local, Public &c variables are listed with their names and values. There is other code in the error routine which shows what tables and indexes are in use.

I would like the report also to show the values of variables which are properties of the form where the error occurred; these can be seen at design time with Form | Edit property/method – the items of type ‘P’.

There would usually be no more than half a dozen of these : things like the account number of a customer being processed &c

When the error occurs, the command AMEMBERS(laProp, ) produces an array which includes these items, and this includes the names of the properties which I would like to show (with their current values) on the error report. But it also includes a large number of properties of the parent classes, which I do not usually wish to include in the report.

Would be grateful if anyone can provide code which would list the properties of the current form and their values.

- Andrew
 
Well, you mention AMEMBERS, look at its documentation, you have the parameter cFlags, which enables you to condense it down to the properties you want. Besides that the array data will tell you what the members are, whether they are properties, methods or events, besides more.

And then you always also have the Error event of a form. If you program that, you can still call to the function ON ERROR points to, but in the first stage you have code in the context of the form, where the error occurred, and can do very form-specific things, including selecting which of the form properties you want to add into the error report, reading them out and putting them together. Make use of that, too.

Chriss
 
Andrew,

Have you studied the values of the fourth parameter (the "flags" parameter) to AMEMBERS()? This let' you filter the items that the function returns. In particular, a flag of "U" will return "User-Defined (Extrinsic) properties, methods, or events". Is that what you want (possibly combined with other flags)?

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
And, if that doesn't give you what you want, you could first use AMEMBERS() to get all the properties (and their values) into the array, then loop through them, calling PEMSTATUS() for each one, passing 4 as the last parameter. That will tell you whether the property in question is user-defined.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Andrew,

Given than you are doing this from within an error handler, the first thing you need to do is to get an oobject reference to the form whose properties you are interested in. You can't use THISFORM, as that is only available from within the form itself. Instead, do something like this:

Code:
IF _screen.FormCount > 0
  oForm = _screen.ActiveForm
ELSE
  * No form present
ENDIF

Once you have the object reference, you can get the names of the custom properties like this:

Code:
lnCount = AMEMBERS(laProps, oForm)
lnCustomCount = 0
FOR lnI = 1 TO lnCount
    IF PEMSTATUS(oForm,  laProps(lnI),  4)
       lnCustomCount = lnCustomCount + 1
       DIMENSION laCustomProps(lnCustomCount)
    ENDIF
ENDFOR

And just to be clear: I am assuming that what you refer to as "local properties" are in fact custom properties, that is, properties other than built-in properties. Is that right.

The above code will first get all the properties into laProps. Then it will loop though laProps, looking for those properties that are custom properties (that's what the "4" in the third parameter to PEMSTATUS() tests for). It puts those it finds into array laCustomProps.

I haven't tested the above code, but I think it should work (more or less).

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Mike, as you pointed out cFlags, like I did, it makes sense to also demonstrate it. You can get a list of user defined properties by

Code:
Clear  
oForm = CreateObject("myform")
For nCount=1 to AMembers(laProps,oForm,3,[highlight #FCE94F]"U"[/highlight])
? laProps[nCount,1],laProps[nCount,2],laProps[nCount,3],laProps[nCount,4]
? GetPem(oForm,laProps[nCount,1])
EndFor 
oForm.newmethod()
For nCount=1 to AMembers(laProps,oForm,3,[highlight #FCE94F]"U+C"[/highlight])
? laProps[nCount,1],laProps[nCount,2],laProps[nCount,3],laProps[nCount,4]
? GetPem(oForm,laProps[nCount,1])
EndFor 
Define Class myform as form
  anewproperty = 0
  anotherproperty = "Hello, World"
  Procedure newmethod(x,y,z)
     This.newproperty=42
  Endproc
EndDefine

The second AMEMBERS call demonstrates how to limit it to user defined properties that have changed from their default value. Just take a look into the documentation to learn the details.

To also point out how the Error method can help: First you can use Thifrom, then you can determine how to call the ON ERROR handler after doing the local object specific handling:

Code:
Clear

On Error errorhandling(Error(), Message(), Program(), Lineno())

Set TablePrompt off
Use sdfklsjflkdsjfj && trigger On error
oForm = CreateObject('myform')
oForm.newmethod()

Define Class myform as form
  anewproperty = 0
  anotherproperty = 'Hello, World'
  Procedure newmethod(x,y,z)
     This.anewproperty=42
     This.anotherpropertea='Oops'
  Endproc

  Procedure Error()
    Lparameters nError, CMethod, nLine
    
    Activate Screen
    ? nError, CMethod, nLine
    Local lcMessage, lcProperties
    AError(laError)
    lcMessage = laError[2]
    lcProperties = 'anewproperty: '+Transform(Thisform.anewproperty)
    lcProperties = lcProperties+;
                   ', anotherproperty: '+Thisform.anotherproperty
                   
    errorhandling(nError, lcMessage, cMethod, nLine, This, lcProperties)
  Endproc
EndDefine 

Function errorhandling()
    Lparameters nError, cMessage, cProgram, nLineNo, loErrorObject, cErrorObjectproperties
    
    If Pcount()>4
       * called from error method of object
       ? 'Error in'+loErrorObject.class
       ? 'Properties reported from it: '+cErrorObjectproperties
    EndIf
    
    ? 'Standard error handling:'
    ? nError, cMessage, cProgram, nLineNo
EndFunc

You can of course also use AMEMBERS within the Error method, especially if you define such an error method in a base form class any form inherits. In this case, I programmed the error method with the (internal) knowledge of the own properties. An advantage of that is of course, that you can very individually decide what to pass on as cErrorObjectproperties to the general error handler. It could, for example, also be a mix of some user-defined properties and some standard properties, which are still of interest and so this can be much more individual than you can manage with cFlags.

Another idea might be to define a property for the error method, which is a list of properties that the object error method should read out and summarize in the lcProperties variable for the cErrorObjectproperties parameter of the general error handler.

Chriss
 
And just to state some maybe not-so-obvious things:

The error method can also be coded in an SCX, it's not just possible to make use of it in a class. There's just the opportunity of OOP usage to define one form error method for a base form class all your further form classes and also SCXes are based on.

If you only designed SCX based on the normal base form class, you can redefine them to be based on a class with the Class Browser.

And you can create new SCXes with
CREATE FORM Formname as formclass of classlibrary.vcx

Or you set up a template form class in the Options dialog, tab "Forms" so any new SCX will be based on a form class you specify there, once.

Chriss
 
Oops. I missed a bit out of the code I posted above. It should have been as follows:

Code:
lnCount = AMEMBERS(laProps, oForm)
lnCustomCount = 0
FOR lnI = 1 TO lnCount
    IF PEMSTATUS(oForm,  laProps(lnI),  4)
       lnCustomCount = lnCustomCount + 1
       DIMENSION laCustomProps(lnCustomCount)
       [highlight #FCE94F]laCustomProps(lnCustomCount) = laProps(lnI)[/highlight]
    ENDIF
ENDFOR

I've now tested it, and it seems to work.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Chris, it was interesting to read what you said about the Error method. I must admit that I have never used it. One thing that would worry me would be that the error-handling code would be split over multiple places: at the very least, the base form class and the traditional ON ERROR handler. (You would still need an ON ERROR handler, as not all errors occur in forms). On the other hand, I can see its advantages.

I know that Andrew already has a comprehensive ON ERROR handler that does the job well. If the code that I posted here helps to improve that, I suggest that he stays with it.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Mike,

I think that's viewing it too much from the perspective of ease of error handling. The error method could in special case handle the error by counteracting it or in an other way let the application continue to work, just like the catch of a TRY..CATCH.

That's on top of the advantages I see in Andrews case, so it's often valid to think about locally scoped error handling.

You will always want a general ON ERROR routine to get informed at least about any error that slips through the narrow mashes of TRY..CATCH and object error methods.

I chose to forward THIS to the general error handler, so you can also add general logging about the object in there.

Of course it's not forced, it's just an opportunity.

Chriss
 
Andrew,

This is not necessarily to steer you away from using an Error method - as per Chriss's suggestion. But if you want to stay with your existing ON ERROR prog. for all your error-handling, here is a complete routine that you can plug into it. It will store the names and values of the custom properties of the active form into a 2-colum array (names in 1st col, values in 2nd col).

Code:
IF _SCREEN.FORMCOUNT > 0

  * A form is active, so get an object ref to it
  oForm = _SCREEN.ACTIVEFORM

  * Get all the form's PEMs into an array
  lnCount = AMEMBERS(laProps, oForm)

  * Look for the custom properties
  lnCustomCount = 0
  FOR lnI = 1 TO lnCount
    IF PEMSTATUS(oForm,  laProps(lnI),  4)
      lnCustomCount = lnCustomCount + 1

      * Configure a 2-column array to hold the custom props
      DIMENSION laCustomProps(lnCustomCount, 2)

      * Add the name of the custom property in the 1st col.
      laCustomProps(lnCustomCount, 1) = laProps(lnI)

      * And add its value in the 2nd col.
      laCustomProps(lnCustomCount, 2) = ;
        TRANSFORM(EVALUATE("oForm." + laCustomProps(lnCustomCount, 1)))

    ENDIF
  ENDFOR

ENDIF

Note that all the values will be stored as character strings. For example, a logical false will be stored as ".F.". That's probably what you want if you plan to copy these values to your log file. If not, just remove the TRANSFORM() near the end.

[highlight #FCE94F]
EDIT: I said to remove the EVALUATE() near the end. I should have said to remove the TRANSFORM(). I've now corrected it.[/highlight]

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Thank you for your replies, Mike and Chris. Yes, I am pressing on with including code in my the general ErrorRoutine for the application - making laborious progress.

Mike - your code

Code:
IF _SCREEN.FORMCOUNT > 0

. . . looks pretty good. At present I believe it also shows inherited properties – which I don’t wish to include in the error log because there are quite a lot of them. I believe that I also need to include a test for :

Code:
PEMSTATUS(oform, ThisVar, 6)

.. . and reject a property if that is .T.

Andrew
 
AMembers flags includes I for Inherited properties, methods, or events. So if you want user defined and not inherited, just use U without I, that should work out as you need it.

As said, read the documentation about AMembers.


Chriss
 
Code:
IF _SCREEN.FORMCOUNT > 0
. . . looks pretty good. At present I believe it also shows inherited properties

No. Testing FormCount has got nothing to do with properties, inherited or otherwise. It simply checks that there is at least one form on the screen. If you didn't do that, [tt]oForm = _SCREEN.ACTIVEFORM[/tt] would generate an error.

I also need to include a test for :
Code:
PEMSTATUS(oform, ThisVar, 6)
.. . and reject a property if that is .T.

You're right that that will filter on inherited properties. But - and I'm sorry to harp on about this - are you sure you don't mean native properties - rather than "inherited"? Similarly with custom properties rather than "local" properties?

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
I'm also sorry to harp on this, but changing this line you already condense the array to user defined properties in the first place and won't need to check with PEMSTATUS with 6 for inheritance.

Code:
  * Get all the form's PEMs into an array
  lnCount = AMEMBERS(laProps, oForm, 3, "U")

Since the flags parameter for AMEMBERS is also offering "I" for inherited, leaving it off you only get U. There could be an overlap, if you define your own property in some class and then subclass it, that will be both user-defined (not native) and inherited, but inherited from a class which introduced them as new user-defined properties. So I'd say that's fine, even though it is inherited.

You might be after properties that changed from their default values. Well, both AMEMBERS flags and PEMSTATUS can distinguish between default and changed values. User-defined AND changed would mean AMEMBERS(laProps, oForm, 3, "U+C"), which I also used in my sample code. The + (AND) means the property has to fulfill both criteria. It's user-defined and has cahnged.

Again, please see the documentation of the flags and the examples of the help, you can get at what you want more precisely than you think. PEMSTATUS can always be used on each property you get from AMEMBERS to check something as aftermath, but the flags of AMEMBERS can limit what you need to iterate very much in the first place.

I second Mike that "local" isn't very descriptive in context of properties. The help of AMEMBERS and PEMSTATUS use intrinsic and extrinsic. Regarding the scope they are part of the object, which makes all of them "local", no matter if intrinsic or etrinsic. You could also think about the visibility, protected or hidden property values could not be read from the general error handler, which is yet another reason to make use of the Error method, as that runs in the form itself, it can read out protected properties. Hidden ones are only visible to the base class and not in subclasses, which means you inherit them, but only the base class that introduced them can handle them.

Overall the visibility aspect is rarely used, and also a bit broken in its implementation in VFP. But if you'd use it, it would make it sensible to have a specific method in your base classes that cares for hidden properties and at reporting states about them, you'd need to cascade a call to that method in the object and all parent class levels using DODEFAULT(), which would enable you to get at the stuff neither the object instance can get from itself and even less so the error handler can access from outside.

And if you just want to avoid errors within the error handling when accessing properties you're not allowed to access, you can always use TRY CATCH, also within error-handling code, to guard against triggering error handling recursively.

Chriss
 
Probably still needs to be tidied, but I now have this code as part of my ON ERROR routine which helps to include a list of non-inherited properties of the form where an error occurs.

Code:
oForm = _SCREEN.ACTIVEFORM
AMEMBERS(laMemb, oform, 1, "UC")
lSize = ALEN(laMemb,1)
pLocalProps = ""
FOR I = 1 TO lSize
   IF laMemb[I,2] = 'Property'
      ThisVar = laMemb[I, 1]
      lName = "oform." + Thisvar
      lValue = &lName
      lType = VARTYPE(lValue)
      lInherited = PEMSTATUS(oform, ThisVar, 6)
      *  Restrict the search to items which are _not_ inherited
      IF !lInherited .AND. lType $ "CDN"
         DO CASE
           CASE lType = "D"
            lValue = FormatDate(lValue)
           CASE lType = "N"
            lValue = LTRIM(STR(lValue, 12,2))
            ENDCASE
         pLocalProps = pLocalProps + PADR(lName,30) + lValue +CHR(13)
         ENDIF
      ENDIF
   NEXT I

The rest of the ERORROUTINE() includes code such as LIST MEMVAR to show local and public/private variables &c. I believe it is useful to show the form properties as well. And of course the line number, nature of the error, call stack &c

I would also be interested to see what other contributors include in their 'ON ERROR' code. - if they have such a routine.

Andrew
 
Andrew,

Again, I draw your attention to this line:

Code:
oForm = _SCREEN.ACTIVEFORM

As I mentioned earlier, this will crash if there is no form open. You will get an "ACTIVEFORM is not an object" error. That's why I suggested that you include a test for _screen.FormCount.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Well, in very short I store the position of the error in file/linenumber of code(prg,mehtod/event). and then all info I get from AERROR and ASTACKINFO. I don't save memory variables.

Some errors are easy to fix already just knowing this information. Knowing more like the property values of objects can be helpful, but I rarely know how to interpret them without knowing what happened before, so I stopped getting verbose about all you can store about the current state. And stack info does not give you all you need in that regard, too. Think alone about what goes into the first level of the stack. It would be something from main.prg that stays on the stack as long as the application runs. Most often only the next stack level can give you important information, the third level may already be the menu item that started the current form.

I like to analyze a log with COVERAGE and see what ran before getting to the ON ERROR, that's most often giving more insight than just knowing the last state.

And obviously, you ask users what they did for sake of reproducing the error during a debugging session with the debugger. In the end most harder to understand errors only can be understood single stepping through code and observing the interactions until you see how they lead to the error.

Chriss
 
Thank you Mike

You are quite right. I should include this clause before I use oform

Code:
IF _screen.FormCount > 0
  oForm = _screen.ActiveForm
(The earlier code I posted was really just checking out the mechanics of reading the non-inherited properties of a form.)

And thank you ChrisS. When I look at the error logs (my code produces lots of errors), I wonder whether my Errorroutine() may be rather bulky. An reviewing it. - Andrew
 
It boils down to this: If the line itself doesn't reveal an obvious problem when you look at it, most often all additional info about the current state only reveals why it errored this time, but not generally. Usually it's code that compiles at least, so syntax errors are out.

What helps more is logging states as the evolve and therefore not only coverage logging is helpful. Just a simple old log file you add to manually yourself when you reach (successfully) a certain stage of whatever you do.

The user story also is mostly helpful, even though they might not recall what they did, but if it happens to many users the picture often becomes clearer from many reports of them. If they are active peer to peer you might even find a user not having the problem, because he achieves the same goal with other means in your application. So offering same features in multiple ways isn't only adding to the individual needs and ways users like to use your application, but makes it more stable as users can adapt a workaround.

All in all the most important information to lookup the line of code failing is the position of the error, nothing else, even the simplest error handler is good enough to get that information.

Your idea to log some property values surely comes from a motivation like asking "if I only knew what this property was when the error happened". Well, then of course log it with the error, nothing wrong about that. I also don't often get a good initial reaction if I propose that I add to the logging and error handling to find out more about the problem and ask users to intentionally reproduce the error to get this extended log and error information. But when it helps customers become more open to such intermediate versions and to logging in general, though it takes disk space and time to just preventively log what happens, it's most of the time very helpful.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top