Welcome to Foxite.COM Community Weblog Sign in | Join | Help
<March 2010>
SuMoTuWeThFrSa
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

Navigation

Syndication




FarPoint Spread 7

Recently I have a project which requires a grid more powerful than the one comes with VFP. So I begin to research on 3rd party ActiveX COM grid controls. Eventually I purchased a copy of FarPoint Spread 7 (http://www.fpoint.com/products/spread/spread.aspx).

How I know of FarPoint Spread 7? A while back while I was taking up the task of maintaining an existing VB6 project, I found out the original programmer used an old version of FarPoint Spread. My client doesn't have the source disk of the FarPoint Spread any more, so I emailed FarPoint technical support and asked for it. They sent me the installation files right away! I am impressed with their technical support because that is a really old version of the control.

Although it is not cheap, but to me it certainly worth the price. I don't have any issue using it with VFP, not a single crash. Having said that, FarPoint grid is not 100% compatible with VFP. For example, any grid method with an out parameter in its signature is not compatible with VFP. It is a known issue (http://support.microsoft.com/kb/177575). Fortunately there are workarounds so I don't see that as a showstopper.

Their online documentation is also very good. Although they have a trial version of the control, I found that even if I don't download the trial, I can still evaluate the control based on their online doc.

Well, I guess all COM control vendors are moving to .NET now. So I don't expect there will be a next version of FarPoint Spread COM control. But I still find this control as of what it is now very useful to my present and future VFP projects.

In short, I am a satisfied user of the FarPoint Spread 7 control.


posted Saturday, June 28, 2008 4:53 AM by davidfung | 4 Comments

Excel 2007 and DBF
To continue the saga from my previous post (Excel Event Binding), the custom application I wrote for my client is now running happily with Excel 2003 after implementing those new event handlers to satisfy Excel 2003.  So far so good.  But that is not the end of the story.

New computers at my client site come with Office 2007.  (I don't know where you can buy Office 2003 anymore.)  Excel 2007 again has some new events that need to be handled:

WorkbookRowsetCompleted()
AfterCalculate()

After adding these event handlers, Excel 2007 OLE Automation has no problem. 

However, my client application manipulates a lot of DBF files.  (Please don't ask me to upgrade the DBF to some sort of SQL databases, as in my client's case it does not make sense -- trust me! :)

I found out some features of the app which automate Excel to save in dBASE III format do not work anymore, and after some googling, I became aware that Excel 2007 cannot save in DBF format any more.

Haha, it comes to a dead end. 

On a side note, in the past, when I want to automate Excel, I just start up Excel and record a macro of the actions I like to do, and then view the auto-generated macro to retrofit the code into VFP.  Sweat and easy.

I tried to do the same in Excel 2007, say inserting a picture into a doc, and guess what, the recorded macro is empty.  Reading the help file it says navigations in the Ribbon Bar will not be recorded.  (To tell you the truth, I am a bit shock when I read that :P)  Unfortunately, I don't know how to insert a picture into a doc without using the Ribbon Bar. 

So I have to record a macro in Excel 2002/2003 and then try the generated code with Excel 2007 to see if it works or not.  Is there an easier way?

And of course, the idea of using Excel to repair a DBF I blogged about earlier on does not work any more...

Why things become so difficult in Excel 2007?  Or is it just me? :)
Btw, I am using VFP9 SP2.

posted Wednesday, November 28, 2007 11:21 AM by davidfung | 40 Comments

Excel Event Binding
I do a fair amount of Excel Automation work for my client.  This time I would like to use Excel as an external editor to compliment the VFP built in grid, i.e. instead of using the grid to edit data, the user can say click a button to start up Excel and use Excel to edit the data, when they close Excel after editing, somehow the edited data will be wrriten back to the grid.  This way the user can use the multi-cell copy and paste feature of Excel among other useful features of Excel.

In order to do that, I need to trap the Excel closing event.  That is doable with the EventHandler call.  All I have to do is to find out which Excel interface to bind to by loading the Excel type library into VFP object browser.  I picked AppEvents in Excel.Application and bind to the AppEvents_WorkbookbeforeClose event.  That all works fine until I release it to Production.  The EventHandler() call failed complaining an event handler is missing, namely AppEvents_WorkbookSync.  I eventually found out that it is because of different version of Excel, I have v2002 in my development PC, and the production PCs have v2003.

Okay, I tried to copy the Excel.EXE from a production PC to my development PC and open it up in object browser to check how different the AppEvents interface is.  When I try to ask VFP to create a skeleton class for me by dragging the AppEvents node in the object browser to a code window, it errors out with:

"Member VARTYPEINFO does not evaluate to an object."

If I click ignore, then another error:

"Variable "LCINTCODE" is not found.

No code is generated.

So eventually I have to manually code back all the missing event handler in order to make it work with Excel 2003.  These functions do not have to do anything as long as they are defined to satisfy the binding call.  These missing event handlers are:

AppEvents_WorkbookSunc()
AppEvents_WorkbookBeforeXMLImport()
AppEvents_WorkbookAfterXMLImport()
AppEvents_WorkbookBeforeXMLExport()
AppEvents_WorkbookBeforeXMLExport()

The exact definition of these functions can be found in MSDB website.
Perhaps I may have to to do all these over again when my client starts to use Office 2007...
Or perhaps there is a simply way to solve this problem...




posted Monday, November 12, 2007 10:43 PM by davidfung | 1 Comments

The Language C
The language C is behind a lot of fundamental technologies such as Linux, apache, device drivers, internet protocols, etc.  The first version of BitTorrent is written in Python and Python is written in C.  Ruby on Rails is written in Ruby and Ruby is written in C.  Yet most of us is not concerned when will be the next major version upgrade of the Language C.  Why?

What makes C so powerful is that we can write libraries (in C) to extend C.  Similarly in the case of Perl, it takes a long time to go from Perl 5 to Perl 6, but that doesn't harm the usefulness of Perl 5.  Because a vast number of extension modules are written to extend Perl 5 in Perl (and in C). 

VFP is no exception.  VFP is a mature product that innovation goes sideway.  I really appreciate the VFP team to make extensibility and compatibilty the two major goals of VFP beyond version 9.  Although closed source, VFP is open enough to be extended by VFP (xBASE) itself, by DLL written in any language, and by FLL written in C.  VFP is very friendly with C and I guess only when C demises VFP will demise.



posted Wednesday, September 05, 2007 4:14 PM by davidfung | (Comments Off)

Disappearing Excel Data Validation

The other day I have some records from a DBF that need to be exported to an Excel file.  I also need to add some list-based data validation in Excel via automation.  I am using VFP9 and Excel 2002.  Below is basically what I do.

export to c:\sheet.xls type xl5
loExcel = createobject('Excel.Application')
loExcel.Workbooks.Open("c:\sheet.xls")
loExcel.Columns("H").Validation.Add(xlValidateList, xlValidAlertStop, ...)
loExcel.Activeworkbook.Save()
loExcel.Quit()

I had verified the above code works in an interactive session in the command window.  But when I open the generated spreadsheet, the data validation is gone!

Eventually I found out that VFP exports to Excel v5 format, which does not support Data Validation.  However I can add the data validation in the spreadsheet because I am using Excel 2002.  The Save() save in the existing format which is v5 which removes the data validation code without warning.

So I have to do a SaveAs() to save the generated spreadsheet in Excel 2002 format which keeps the data validation code:

loExcel.Activeworkbook.SaveAs('c:\sheet2.xls',xlDefault)

It would be better if there is a way for VFP to export DBF to a more update version of Excel without requiring an installed copy of Excel.


 

posted Friday, May 25, 2007 11:54 PM by davidfung | (Comments Off)

Passing a parameter object to a form

We can pass info to and retrieve info from a form like this:

   DO FORM WITH arg1,arg2 TO result

which is similar to calling a function:

   result = FORM(arg1,arg2)

When I am developing a form, I often need to change the number of input and output arguments, which means I need to constantly adjust the form calling interface. 

By using a parameter object as an input/output mechanism simplifies it like below:

local loParam
loParam = createobject('empty')
addproperty(loParam,'input',"input_value")
addproperty(loParam,'output')
do form with loParam
? loParam.output

Also the use of parameter object makes it flexible in when and where to assign the output value, i.e. not restricted to RETURNing a single entity in the UNLOAD event of the form.

I have made a screencast showing that here at ShowMeDo.com.

 

posted Thursday, March 29, 2007 7:34 PM by davidfung | 1 Comments

A class to encapsulate ON ESCAPE

Often times I need to break out from a loop if user hit the escape key.  In order to do that, will have to:

  1. Save the existing state of the SET ESCAPE
  2. Set up ON ESCAPE
  3. Restore SET ESCAPE afterward

To those not familiar with VFP commands and how VFP does things, it may be difficult to figure this logic out.  I have encapulated all this into a class to make it easier to understand what is happening.

 loEscToAbort = createobject('syEscToAbort')
 for i = 1 TO infinity
    if loEscToAbort.IsAborted()
       exit
    endif
 endfor
 release loEscToAbort

The class is:

define class syEscToAbort as relation
  
   hidden icOldEscape
   hidden ilAbort
   hidden ilEscapeHit
  
   icOldEscape = ""
   ilEscapeHit = sys(2015)
  
   function init
      this.icOldEscape = set("Escape")
      set escape on
      public (this.ilEscapeHit)
      this.reset()
      execscript([on escape ] + this.ilEscapeHit + [ = .t.])
   endfunc
  
   function destroy
      release (this.ilEscapeHit)
      lcOldEscape = this.icOldEscape
      set escape &lcOldEscape
      on escape
   endfunc
  
   function isAborted()
      local llAborted
      llAborted = evaluate(this.ilEscapeHit)
      return llAborted
   endfunc
  
   function reset()
      execscript(this.ilEscapeHit + [ = .f.])
   endfunc
enddef

Note that:

  1. The class creates a global variable during its lifetime.
  2. The original ON ESCAPE command is not preserved.

 

posted Thursday, March 15, 2007 10:13 PM by davidfung | 1 Comments

Automating Installer Build Process

I use INNO Setup to build the installer for my app.  I like to show the version number (from the version resource in the compiled EXE) and the built date in the installer as show below:

Since INNO Setup is script based, that can easily be done as below:

But I don't want to manually update the INNO script every time I have a new build, so I automate it.  I already have a make.prg which I run to build my project, all I have to do is to enhance it to update the version and timestamp automatically. 

That boils down to the following steps:
1. using AGetFileVersion() and fdate() on the EXE to get the version number and the built date
2. check out the INNO script from SourceSafe
3. stuff it to the INNO script using filetostr(), strextract(), strtran() and strtofile()
4. check in the INNO script back to SourceSafe
3. call the INNO Setup Compiler to build the installer

So by issuing DO MAKE at the VFP command window, I will have the installer automatically built with the correct version and timestamp, and also have the latest INNO setup script in source control.  The fact that both INNO Setup and SourceSafe have command line interface make things much easier, not to mention the readily available VFP built-in commands.

Attached is the make.prg in its entirety.

make.prg

posted Thursday, March 01, 2007 4:34 PM by davidfung | 3 Comments

Backward Compatible

I am always fascinated by the backward compatibility of VFP, and that really helps me to do my job with much less headache.  I am a solo developer who not only have to build the app, but also have to support it.  Being backward compatible make it possible for me to gradually "upgrade" my app at a pace that my client and I can afford.  SET BEHAVOIR allows you to control the feature globally, where as individual commands such as REPORT FORM allows fine-grained control. 

I "re-use" this idea in my app.  Whenever I introduce some controversial changes to my client's app, I condition it with an ini entry, so that i can switch back and forth with or without the change easily.  This gives me a peace of mind because if the new feature doesn't work or my client changes thier mind, I don't have to rush to my client's office to fix it.  I can fall back instantly without any code change, recompile or testing.  This buys me time.

In the code, rather than modifying the original source code to add the new feature, I clone the existing code first and then modify the clone to add the new feature, and then condition which portion of the code to run with the value of the ini entry.  This way I know for sure the old code will work as before (because I haven't changed it) in the case I have to fall back to it.  This reduces the amount of regression testing.  Rolling it back to a previous version from source control will also work but I generally find that more troublesome to do in practice.

In order for this to work, the new feature will have to be designed properly so as to make it compatible with the old feature.

With this approach, both my client and myself are happier because I become less reluctant to accept change requests, and my client is welcomed to change their mind back and forth.

 


 

posted Monday, February 05, 2007 8:41 PM by davidfung | (Comments Off)

Two faces of an application


I have two different VFP apps running at a client site.  One is a frontend desktop app that the end-users use, and the other one is a backend maintenance app.  The maintenance app runs at night to do a set of system maintenance jobs.  It has no frontend and if there is anything needing attention, it will email the technical support personnel for further investigation.

I developed these two apps separately, i.e. they belong to two different projects.  In the beginning, the maintenance app only did a few things, such as search and delete temp files, check diskspace, etc.  Over time, more and more functions are added to it, and it became more intimate with the internal logic of the frontend desktop app.

Rather than duplicate code, I tried to share the common source files between the two projects but the interdependence among those source files force me to include more source files, and here and there need to add some conditional logic to make them compile.

This smells ugly, and I now decide to bite the bullet to integrate the maintenance app logic into the desktop app, so that all the library routines in the frontend app are available to the backend app.  This integrated app runs dual mode, i.e. as a frontend app or a backend app, probably depends on a command line switch.

I now begin to see the ease of maintenance - one project to maintain, and one branch in the source tree!  This will be the way to build future custom apps, at least for me that is.

Another way to do it is to build a third project housing the common routines, but it is kind of late at this stage because a great deal of refactoring may be required in order to extract those common routines into a separate project. 

 

 

posted Saturday, October 28, 2006 1:43 PM by davidfung | (Comments Off)

Use Excel to repair DBF
My app has to deal with a lot of DBF which sometimes get corrupted.  Upon checking, the corruption is most likely due to some additional bytes after the logical end of the file.  The problem is VFP9 refuses to open it with error 2091:

Table "name" has become corrupted. The table will need to be repaired before using again.

It always puzzle me why VFP does not any built-in DBF repair command (not even the simple one) although it has a native DBF engine.

Lately the frequency of corruption get to a point becomes annoying.  So I look around the Net to see if there is any DBF repair utility which I can incorporate into my app to fix the corruption automatically when it is detected.

There are some but it is not apparent that they support command line execution or OLE Automation, and they are not cheap.  My client probably doesn't like absorbing the cost of it.

But my client has Excel installed in every workstation, and I noticed that Excel can open the corrupted DBF okay without any problem.  So I wrote a routine to use Excel to repair the DBF.  It simple asks Excel to open the corrupted DBF and save it again.  It probably will not repair all kinds of corruption, but at least it works in my case.  Please see if it works for you as a DBF Repair alternative.

*------------------------------------------------------------------------
* ut_RepairDBF()
*
* Call this routine to repair a DBF file reported by VFP to be corrupted.
*
* PASSES:
*   tcFile = The full path to the DBF file to be repaired.
*            e.g. 'c:\path\table.dbf'
*
* RETURNS:
*   .t. if successful, .f. otherwise.
*
* OUTPUT:
*   The original DBF is backup with .bak extension.
*   The repaired DBF has the same name as the original file.
*  
* EXAMPLE:
*   ut_RepairDBF('c:\path\table.dbf')
*
* REMARKS:
*   Microsoft Excel is used to repair the DBF.
*------------------------------------------------------------------------
function ut_RepairDBF(tcFile)

   local loExcel as Excel.Application
   local lcBackupFile
   local lcFixedFile
  
   if empty(tcFile) or not file(tcFile)
      return .f.
   endif
  
   loExcel = createobject("Excel.Application")
   if vartype(loExcel)<>'O'
      return .f.
   endif
  
   lcBackupFile = alltrim(tcFile)+'.bak'
   lcFixedFile = alltrim(tcFile)+'.tmp'

   delete file (lcBackupFile)
   delete file (lcFixedFile)
  
   loExcel.Workbooks.Open(tcFile)
   loExcel.ActiveWorkbook.SaveAs(lcFixedFile, xlDBF3)
   loExcel.ActiveWindow.Close(.f.)
   loExcel.Quit()
  
   rename (tcFile) to (lcBackupFile)
   rename (lcFixedFile) to (tcFile)
endfunc
 

posted Tuesday, October 03, 2006 4:55 PM by davidfung | 6 Comments

When a report is printed

I would like to do something (update a table) when a report is printed from the preview windows.

The old way of setting a variable in the report and checking it afterward does not work with an object-assisted report because sys(2040) is not designed to work with the new behavior of the new reporting mechanism of VFP9.

llPrinted = .f.
REPORT FORM xxx TO PRINTER PROMPT OBJECT TYPE 1
IF llPrinted
   ? "Printed"
ENDIF

and in the report set llPrinted to .t. if sys(2040)="2".  llPrinted is always .f. even when the user prints the report.

After a few message exchanges with Borislav Borissov, Cathy Pountney, Colin Nicholls and Agnes Beste in UT, Agnes pointed me to the direction of using the OnPreviewClose event of the reportlistener to get the job done.  I have packaged the technique into a generic base class clsReport where different reports can be subclassed from.  The following code demonstrates the concept:

if not file('rptCustomers.frx')
   select 0
   use home(2)+'northwind\customers' shared noupdate again
   create report rptCustomers.frx from alias()
   use
endif

ox = createobject('clsCustomersReport')
ox.icReportName = 'rptCustomers'
ox.preview()
release ox

define class clsReport as session
 
   icReportName = ""
   
   function preview
      local loListener as ReportListener
      loListener = newobject('reportlistener')
      loListener.ListenerType = 1
      bindevent(loListener,'OnPreviewClose', this, 'OnPreviewClose')
      report form (this.icReportName) object loListener
      unbindevent(loListener,'OnPreviewClose', this, 'OnPreviewClose')
   endfunc

   function OnPrinted()
   endfunc
  
   function OnPreviewClose( tlPrinted )
      if tlPrinted
         raiseevent(this,'OnPrinted')
      endif
   endfunc
  
enddefine

define class clsCustomersReport as clsReport
   function OnPrinted()
      messagebox('Printed')
   endfunc
enddefine

Please note that in this case the report is regarded as printed when it goes to the print spooler, even though it may not necessarily be physically printed (e.g. due to paper jam, user cancel print job, etc)

 

posted Wednesday, September 13, 2006 11:24 AM by davidfung | (Comments Off)

Data Explorer and Sorting

I use Data Explorer (DX) to browse a SQL Server 2000 database in a project.  If I don’t select Sort Objects in the SQL Connection Properties dialog, then I get a list of all the SPROC in random order when I expand the stored procedure node.

 

 

If I select Sort Objects, then I get an empty list!

 

 

Northwind does not have this problem.  Only my database has this problem.  So I dig into XSource and narrow down the problem to this line of code in SQLDatabaseMgmt.OnGetStoredProcedures() defined in datamgmt_sql.prg

 

TEXT TO cSQL TEXTMERGE NOSHOW PRETEXT 7

SELECT * FROM

[<<THIS.DatabaseName>>].INFORMATION_SCHEMA.ROUTINES

WHERE ROUTINE_TYPE = 'PROCEDURE'

        <<IIF(THIS.SortObjects, "ORDER BY [ROUTINE_NAME]", "")>>

ENDTEXT

 

After text merged, the sql becomes:

 

SELECT * FROM [Database].INFORMATION_SCHEMA.ROUTINES

WHERE ROUTINE_TYPE = 'PROCEDURE'

ORDER BY [ROUTINE_NAME]

 

which returns the following error:

 

Cannot sort a row of size 8295, which is greater than the allowable maximum of 8094.

 

When I change it to select a subset of the fields instead of all fields:

 

SELECT Routine_Name, Routine_Schema FROM [Database].INFORMATION_SCHEMA.ROUTINES

WHERE ROUTINE_TYPE = 'PROCEDURE'

ORDER BY [ROUTINE_NAME]

 

It works.  So I change the code and recompile the DataExplorer to get it works (Method OnGetFunctions of the same class has the same issue as well). 

 

It didn't work before for my database but works for Northwind may be because my database is originally created in Taiwan locale which makes its INFORMATION_SCHEMA.ROUTINES records having a row size > 8094.

 

posted Saturday, August 19, 2006 6:57 AM by davidfung | (Comments Off)

Distributable Command Window

Adel Gharib has submit a tip of the month in Advisor Guide to Visual FoxPro July 2006 issue to create a distributable command window which can be embedded into a compiled VFP exe.  I have worked on Adel's tip and package the code into a class (commandprompt of sysadmin.vcx).

I use the ACCEPT command instead of the INPUTBOX command suggested by Ceil because it acts more like a command window that way.  And a form is used as the input/output service. 

To open the command prompt window, run the following code in the VFP command window or through a menu pad or in a mouse click event handler:

   ox = newobject('commandprompt','sysadmin')

Here is a screenshot of it in action.

Click here to download the VCX and below is the source code of it:

DEFINE CLASS commandprompt AS form

 Height = 250
 Width = 467
 ShowWindow = 2
 DoCreate = .T.
 AutoCenter = .T.
 Caption = "Command Prompt"
 FontName = "Courier New"
 FontSize = 10
 BackColor = RGB(255,255,255)
 Name = "commandprompt"

 *-- Start the command prompt.
 PROCEDURE start
  #define EXIT_TOKEN 'quit'

  LOCAL lcPrompt, lcCmd
  LOCAL loEx as Exception

  this.Caption = "Type '" + EXIT_TOKEN + "' to finish"
  this.Show
  ? version()
  ? os(1) + " " + os(7) + " " + sys(17) + " " + strtran(sys(0)," #","")

  DO WHILE .t.
     lcPrompt = SYS(5)+SYS(2003) + IIF(EMPTY(ALIAS()),""," (" + ALIAS() + ")") + " > "
     accept lcPrompt to lcCmd
     IF ALLTRIM(LOWER(lcCmd)) == EXIT_TOKEN
        EXIT
     ELSE
        TRY
           EXECSCRIPT(lcCmd)
        CATCH TO loEx
           MESSAGEBOX(loEx.Message,0, "Error " +tran(loEx.ErrorNo))
        ENDTRY
     ENDIF
  ENDDO
 ENDPROC

 PROCEDURE Init
  this.Show
  this.start
  return .f.
 ENDPROC

ENDDEFINE


 

posted Sunday, July 30, 2006 1:13 AM by davidfung | (Comments Off)

MySQL GUI frontends

I haven't done any client/server work with VFP yet but plan to do so.  I am thinking of should I use MSDE or MySQL. 

MySQL now has GUI frontends similar to Enterprise Manager and Query Analyser.

MySQL Admin
http://www.mysql.com/products/tools/administrator/

MySQL Query Browser
http://www.mysql.com/products/tools/query-browser/

I haven't tried them yet but their screenshots look promising.  With these tools, it would be easier to do ad-hoc maintenance job at the client site.  This is a plus for MySQL.  On the other hand, MSDE does not come with any GUI front end, so it will be more difficult to manage it at the client's site, which is a factor that I will consider which one to pick.

posted Friday, July 28, 2006 1:55 PM by davidfung | 7 Comments

More Posts Next page »
Powered by Community Server, by Telligent Systems