[Company Logo Image] 

Home Up Feedback

Tips and Tricks
MS Knowledge Base Tips and Tricks SlashPane Wiki Mirror

 

You can find more of my VFP tips, tricks, and memories on my blog.

  • Alias use with VCXs
  • Are you connected?
  • COMRETURNERROR and TRY...CATCH
  • String "optimization" and recursive algorithms
  • Create SQL statements with Intellisense
  • Parsing web log files

    Alias use with VCXs

    Modified 2003-02-18

    Thanks to Derek J. Kalweit for pointing out this technique. In VFP 6.0 (at least) and above, if you have a class with the same name in two class libraries, you can refer to the class with the classlib prefixing the class name in the CREATEOBJECT() function.

    loTxt1 = CREATEOBJECT("Textbox")
    loTxt1.SaveAsClass("alias.vcx", "txtAlias")
    
    loTxt2 = CREATEOBJECT("Textbox")
    loTxt2.Width = loTxt2.Width * 2
    loTxt2.SaveAsClass("alias2.vcx", "txtAlias")
    
    SET CLASSLIB TO alias, alias2
    loTxtAlias = CREATEOBJECT("alias.txtAlias")
    ?loTxtAlias.Width && 100
    
    loTxtAlias2 = CREATEOBJECT("alias2.txtAlias")
    ?loTxtAlias2.Width && 200
    
    SET CLASSLIB TO
    
    *!* The NEWOBJECT function allows you to remove any ambiguity
    *!* between the classlibs, as well as permitting the use of
    *!* PRG class definitions as well.
    loTxtAlias = NEWOBJECT("txtAlias", "alias.vcx")
    ? loTxtAlias.Width
    
    loTxtAlias2 = NEWOBJECT("txtAlias", "alias2.vcx")
    ? loTxtAlias2.Width
    
    *!* The following syntax for creating the class definition will 
    *!* only work in VFP7. The NEWOBJECT call, however, will work in 6,
    *!* if you create the .prg manually.
    TEXT TO lcClassDef NOSHOW
    DEFINE CLASS txtAlias AS TextBox
       Width = 300
    ENDDEFINE
    ENDTEXT
    
    STRTOFILE(lcClassDef, "alias3.prg")
    
    loTxtAlias3 = NEWOBJECT("txtAlias", "alias3.prg")
    ? loTxtAlias3.Width && 300
    Top

    Are you connected?

    The following code will help you determine if you're connected to the internet. However, for connections that are "always on", it may tell you you're online, even if you actually aren't.

    DECLARE SHORT InternetGetConnectedState IN wininet ;
       LONG @ lpdwFlags, LONG reserved
    
    lnFlags = 0
    reserved = 0
    
    ? InternetGetConnectedState(@lnFlags, reserved)
    ? lnFlags

    lnFlags consists of the following constants ORed together.

    #define INTERNET_CONNECTION_MODEM 0x01
    #define INTERNET_CONNECTION_LAN 0x02
    #define INTERNET_CONNECTION_PROXY 0x04
    #define INTERNET_CONNECTION_MODEM_BUSY 0x08 /* no longer used */
    #define INTERNET_RAS_INSTALLED 0x10
    #define INTERNET_CONNECTION_OFFLINE 0x20
    #define INTERNET_CONNECTION_CONFIGURED 0x40

    You can find them in WinInet.h, which installs with Visual C++.

    If all else fails, the following code will work, if inelegantly (as long as your firewall allows ping packets through).

    RUN ping www.google.com > c:\ping.txt
    lcPingResult = FILETOSTR("c:\ping.txt")
    ?AT("Reply", lcPingResult) != 0 
    Top

    COMRETURNERROR and TRY...CATCH

    Created 2003-02-18

    Randy Pearson put together an excellent matrix explaining how different types of error handling work together (TRY...CATCH, Object.Error, ON ERROR). However, there was one thing he left out: error handling across process boundaries, from a COM DLL to a VFP app. The code below shows several variations of error handling, ending with an example of how you might use COMRETURNERROR with structured error handling to return a THROWn error from an MTDLL.

    #DEFINE vfpCRLF CHR(13) + CHR(10)
    LOCAL lcOldSafety, lcThrowTest, lcThrowTest2, loTT1, loTT1a, loTT2, loTT2a, ;
       loErr as Exception, loCaught as Exception, llNoError
    
    lcOldSafety = SET("Safety")
    SET SAFETY OFF 
    *!* Here we create the class that intentionally THROWs an error 
    *!* in its BadCall method.
    ***************************START TEXT***************************
    TEXT TO lcThrowTest NOSHOW
    #DEFINE vfpCRLF CHR(13) + CHR(10)
    DEFINE CLASS ThrowTest AS SESSION OLEPUBLIC
    *!*	   PROCEDURE Error
    *!*	      LPARAMETERS nError, cMethod, nLine
    
    *!*	      IF INLIST(_VFP.StartMode, 3, 5)
    *!*	         COMRETURNERROR("DLL Error Event", ;
    *!*                 "Error " + TRANSFORM(nError) + " in " + cMethod + ", line " + TRANSFORM(nLine))
    *!*	      ELSE
    *!*	         MESSAGEBOX("ERROR METHOD" + vfpCRLF + vfpCRLF + ;
    *!*	            "Error number: " + TRANSFORM(nError) + vfpCRLF + ;
    *!*	            "Method:       " + TRANSFORM(cMethod) + vfpCRLF + ;
    *!*	            "Line Number:  " + TRANSFORM(nLine) + vfpCRLF)
    *!*	      ENDIF
    *!*	   ENDPROC
    
       PROCEDURE BadCall
          LOCAL loErr AS Exception
    
          TRY
             ERROR 1
          CATCH TO loErr
             IF INLIST(_VFP.StartMode, 3, 5)
                THIS.Error(loErr.ErrorNo, loErr.Procedure, loErr.LineNo)
             ELSE
                THROW loErr
             ENDIF
          ENDTRY
       ENDPROC
    ENDDEFINE
    ENDTEXT 
    ****************************END TEXT****************************
    
    STRTOFILE(lcThrowTest, "throwTest.prg")
    
    loTT1 = NEWOBJECT("ThrowTest", "throwTest.prg")
    
    *!* First, we'll just call it as a local object, with only TRY...CATCH.
    TRY
       loTT1.BadCall()
    CATCH TO loErr
       loCaught = loErr.UserValue
       
       WITH loCaught
          MESSAGEBOX("CAUGHT ERROR" + vfpCRLF + vfpCRLF + ;
             "Error number:  " + TRANSFORM(.ErrorNo) + vfpCRLF + ;
             "Message:       " + TRANSFORM(.Message) + vfpCRLF + ;
             "Line Number:   " + TRANSFORM(.LineNo) + vfpCRLF + ;
             "Line Contents: " + .LineContents)
       ENDWITH 
    ENDTRY
    
    *!* Now, we'll compile it into an MTDLL, and see what we can catch here.
    BUILD PROJECT ThrowTest FROM throwTest.prg
    BUILD MTDLL ThrowTest FROM ThrowTest
    loTT1a = CREATEOBJECT("ThrowTest.ThrowTest")
    
    llNoError = .T.
    TRY
    	loTT1a.BadCall()
    CATCH TO loErr
    	MESSAGEBOX("This code won't execute: TRY...CATCH won't go across " + ;
    	           "the process boundary.")
    	llNoError = .F.
    ENDTRY
    
    IF llNoError
    	MESSAGEBOX("No error was propagated when THROWing from an MTDLL.")
    ENDif
    
    *!* Now, let's uncomment the Error method, and see how that affects matters 
    *!* in each case.
    lcThrowTest2 = STRTRAN(lcThrowTest, "*!*", "")
    STRTOFILE(lcThrowTest2, "throwTest2.prg")
    
    loTT2 = NEWOBJECT("ThrowTest", "throwTest2.prg")
    
    TRY
       loTT2.BadCall()
    CATCH TO loErr
       MESSAGEBOX("This code won't run: the Error method will trap the error.")
    ENDTRY
    
    
    BUILD PROJECT ThrowTest2 FROM throwTest2
    BUILD MTDLL ThrowTest2 FROM ThrowTest2
    
    loTT2a = CREATEOBJECT("ThrowTest2.ThrowTest")
    TRY
    	loTT2a.BadCall()
    CATCH TO loErr
       WITH loErr
          MESSAGEBOX("CAUGHT ERROR" + vfpCRLF + vfpCRLF + ;
             "Error number:  " + TRANSFORM(.ErrorNo) + vfpCRLF + ;
             "Message:       " + TRANSFORM(.Message) + vfpCRLF + ;
             "Line Number:   " + TRANSFORM(.LineNo) + vfpCRLF + ;
             "Line Contents: " + .LineContents)
       ENDWITH 
    ENDTRY
    
    SET SAFETY &lcOldSafety
    Top

    String "optimization" and recursive algorithms

    Created 2003-06-03

    There is an optimization in VFP string handling that allows blazingly fast work with long strings. However, this can cause problems with recursive algorithms that build long strings. To get around it, instead of doing

    lcString = lcString + lcMyNewText

    you can do

    lcString = "" + lcString + lcMyNewText

    That extra null string disables the optimization, so that the normal string handling kicks in, and the recursion doesn't get confused.

    For an example of this in action, check out http://www.capescience.com/webservices/globalweather/GWFoxpro.html - that's the code where I found this bugbehavior. You'll see near the end, where I needed to use lcRetVal = "" + lcRetVal... If you want to try commenting out the null string, feel free. The problem may or may not trigger, depending on the data that's coming back from the web service.

    The actual behavior, as I remember it, is that once you get past a certain level of the recursion, the information on the stack is overwritten by the last data to be added to the string. It's hard to explain properly without showing lengthy examples, so play with the code yourself, until you feel comfortable with your understanding.

    Top

    Create SQL statements with Intellisense

    Created 2004-03-03

    I came up with this routine after typing out a SELECT statement too often. For details on how to add a new custom Intellisense routine to VFP, see the help files.
    LPARAMETERS oFoxCode
    
    #DEFINE vfpCRLF CHR(13) + CHR(10)
    #DEFINE vfpTAB CHR(9)
    #DEFINE vfpINDENT SPACE(7)
    
    LOCAL i, lnFieldCount, lcRetVal, lcField, lcTable, lcAlias
    
    lcRetVal = ""
    oFoxCode.ValueType = "V"
    
    lcTable = GETFILE("DBF")
    *!* If you don't select a table in GETFILE(), use the current table.
    IF EMPTY(lcTable)
    	lcTable = ALIAS()
    ENDIF
    IF EMPTY(lcTable)
    	RETURN ""
    ELSE
    	IF NOT USED(JUSTSTEM(lcTable))
    		USE (lcTable) IN 0
    	ENDif
    ENDIF
    
    lcAlias = ALIAS(JUSTSTEM(lcTable))
    
    lnFieldCount = FCOUNT(lcAlias)
    
    lcRetVal = "SELECT "
    
    *!* I needed to pad all the fields out to 80: you can modify it as necessary.
    FOR i = 1 TO lnFieldCount - 1
    	lcField = LOWER(FIELD(i, lcAlias))
    	lcRetVal = lcRetVal + ;
    		"PADR(" + lcField + ", 80) AS " + lcField + ", ;" + vfpCRLF + vfpINDENT
    ENDfor
    
    lcField = LOWER(FIELD(lnFieldCount, lcAlias))
    lcRetVal = lcRetVal + lcField + " AS " + lcField + " ;" + vfpCRLF
    
    lcRetVal = lcRetVal + vfpTAB + "FROM " + LOWER(lcAlias) + " ;" + vfpCRLF
    
    USE IN (lcAlias)
    
    RETURN lcRetVal

    Parsing web log files

    Created 2004-05-10

    The other day, I was trying to import the log files from NorthshoreCoop.org, so I ran VFP's Import Wizard. Unfortunately, it doesn't work well with the different delimiters in a typical Apache log. So, I gave Excel's Import Wizard a chance at it, with identical results.

    The problem here is that there are three delimiters used: space, space+bracket (for date), and space+quote. So, we need to write some code that can deal with all three of these. ALINES() does a good job of breaking strings down: I use it first to break the log file down into individual lines, and then use it on each line to break it down into space-delimited tokens. The FIELD() function is also useful, as it allows me to make the code very generic, by just letting it know which field I'm in by ordinal (first, second, third) instead of by name.

    I would not be surprised if the file you're trying to parse uses a slightly different format than I had to deal with: I couldn't find any documentation that told me what the last column in the file was. You should be able to just tweak the CREATE CURSOR statement: there are no hard-coded field names or counts after it.

    CREATE CURSOR crsrLog ( ;
     cClientIP C(15), ;
     cIdentity C(20), ;
     cRemoteUser C(40), ;
     cDateTime C(30), ;
     cRequestLine C(80), ;
     cStatusCode C(3), ;
     cSize C(10), ;
     cDomain C(80), ;
     cReferer C(80), ;
     cUserAgent C(80), ;
     cUnknown C(20))
     
    lcLog = FILETOSTR(GETFILE())
    
    *!* Read each line into an array element
    lnLineCount = ALINES(laLogLines, lcLog)
    
    FOR i = 1 TO lnLineCount
     APPEND BLANK
     
     lcLogLine = laLogLines(i)
     
     *!* Break the line down into space-delimited tokens.
     lnTokenCount = ALINES(laTokens, lcLogLine, .T., " ")
     
     lnField = 1
     lcField = ""
     llInString = .F.
    
     FOR j = 1 TO lnTokenCount
      lcToken = laTokens(j)
      
      *!* Does the token start or end with a delimiter?
      llLeft = INLIST(LEFT(lcToken, 1), '"', '[')
      llRight = INLIST(RIGHT(lcToken, 1), '"', ']')
      
      DO CASE
       CASE llLeft = llRight 
        IF llInString
         *!* If we're in the middle of a string, append the token to the string.
         lcField = lcField + " " + lcToken
        ELSE
         *!* Otherwise, just fill in the field and go to the next one.
         REPLACE (FIELD(lnField)) WITH lcToken
         lnField = lnField + 1
        ENDIF 
        
       *!* If the field starts with a delimiter, start building the string.
       CASE llLeft AND NOT llRight
        llInString = .T.
        lcField = lcToken
        
       *!* If the field ends with a delimiter, finish building the string, 
       *!* and fill in the next field
       CASE NOT llLeft AND llRight
        lcField = lcField + " " + lcToken
        REPLACE (FIELD(lnField)) WITH lcField
        lnField = lnField + 1
        llInString = .F.
      ENDCASE 
     ENDFOR
     lnField = 1
    ENDfor
    Top

 

Home ] Up ]

Send mail to webmaster@donnael.com with questions or comments about this web site.
Last modified: 2007-01-29

Join WebHost4Life.com