Outlook.Application

Using the Outlook.Application object to read e-mails from Visual Foxpro.

Trying to read e-mail using POP3 or IMAP proved to be hell to me. Documentation is not present or hardly legible. Because we use Microsoft® Outlook® at our company, I was able to connect to Outlook using the Outlook.Application object.

This simple code finds all mails in a certain folder (loTelecomMap):

loOutlook = CREATEOBJECT("Outlook.application")
loMAPI = loOutlook.GetNameSpace("MAPI")
loTelecomMap = loMAPI.GetDefaultFolder(6).Folders([Business]).folders([Telecom])
loMails = loTelecomMap.Items
loMessage = loMails.Find("[Subject]='Mail subject title'") && starts with the last (newest) message
DO WHILE NOT ISNULL(loMessage)
 ldDate = TTOD(loMessage.ReceivedTime)
 lcMessage = loMessage.Body
 lcHTMLsource = loMessage.HTMLbody
 * More actions on the loMessage object
 loMessage = loMails.FindNext() && Next message
ENDDO
RELEASE loOutlook

I had great help using a whitepaper by Andrew MacNeill. Linking to his page is prohibited, so you have to copy-and-paste the URL yourself: http://www.aksel.com/whitepapers/OutlookAutomation.htm. To accommodate for possible closing of that reference, I have included the pages contents (the whitepaper) in PDF form on this page.

Read from Excel using MS ODBC

If you just want to read the contents of an Excel spreadsheet, there is an easier way using the Microsoft Excel (ODBC) Driver.

In a previous post (Foxpro and Excel) we have shown how to control Excel using the Excel.Application object. But if you are just interested in the contents, there is an even easier way to achieve this: The Microsoft Excel (ODBC) driver. that is present on most systems with MS Office installed.

Source: Importing data from a Microsoft Excel 2007 workbook using Visual FoxPro 9.0 (Microsoft Support)

To get an SQL connection to an Excel sheet is no more difficult than this:

lcXLfile = [c:\temp\demo.xlsx]

* Warning: The driver wil never fail! 
* If the file does not exist, this driver wil create the file.
IF !FILE(lcXLfile)
 ? [Excel file not found]
 RETURN .F.
ENDIF

lcConnection = [DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DBQ=]+lcXLfile
lnHandle = SQLSTRINGCONNECT(lcConnection)
* Get all sheetnames in a cursor (every sheet is a Table)
? SQLTABLES(lnHandle,[],[crSheets])
lcQuery = [SELECT * FROM ]+TRIM(STRTRAN(crSheets.Table_Name,['],["]))
? SQLEXEC(lnHandle, lcQuery, [crResults])
SQLDISCONNECT(lnHandle)

The SQLTABLES command creates a cursor with a row for each sheet in the Excel file. You can filter the kind of tables in the second parameter. This is a typical row:

Field name Field type Length Example
TABLE_CAT Memo 4 C:\TEMP\DEMO.XLSX
TABLE_SCHEM Character 128 .NULL.
TABLE_NAME Character 128 ‘Sheet1$’
TABLE_TYPE Character 128 TABLE
REMARKS Character 254 .NULL.
SQLTABLES structure

Remarks

Quotes in the SQLTABLES result

The TABLE_NAME field contains single quotes, These seem not to work in a SQL statement. So you have to replace them with double quotes. Check out the lcQuery line in the code above..

Field names in queries

Only when a sheet is properly filled the field names in queries are automatically named correctly: The titles in the first row are accepted as fieldnames. However, if the first row is left blank or there are multiple tables on a page, the result becomes erratic.. In this situation some or all fields get the title F1. and so on (number being the column number).

Field types

It seems the unaltered query results deliver just 2 kinds of columns: Numbers and Memo’s. Both columns may contain .NULL. values.

Quotes in query’s

To compare or use strings in SQL query’s, you need to use the single quotes again. For instance:

lcQuery = [SELECT * FROM "Sheet1$" WHERE surname='de Graaf']
SQLEXEC(lnHandle,lcQuery,[crDeGraaf])

Double complex because the sheet (table) name has to be encapsulated in double quotes, while a compare string has to be encapsulated in single quotes.. I can’t think of a reason for this odd behavior.

Read only

As to be expected, this ODBC method to an Excel file is limited to reading the file. Trying to insert or update using SQL commands renders the result that the query has to be updatable.

Installing Visual Foxpro on Windows 10 64bit

After replacing my computer, I needed to reinstall Visual Foxpro. I had the install disc contents, but whatever executable I started with: it kept on crashing. A Google search brought silly advice, but fortunately there was a gem: an article on wOOdy’s Blog. This explained what the right procedure is and what file to start with (VFPSTART.HTA). It stil would not work. Not from a folder, and not from a mapped network disc. I found the free AnyBurn software that gave me the opportunity to create an ISO file from my install folder. Now it was as easy as double clicking the ISO file to have Windows mount the CD image and assign it a drive letter. Now everything worked like a charm. Then we have to apply both service packs, and perhaps some useful suggestions wOOdy made.

Foxpro ReturnObject

Function to prevent problems during object creation to crash your program. When problems arise, your program might be unable to continue, but at least you can show the user what went wrong instead of an uninformative crash warning.

* ReturnObject 
* 2011.05.31 start
* 2012.07.06 parameters added
* Tries to create an object and return this. If object creation failed, a false is returned
* Parameters: Class to instantiate object from, max 2 parameters
FUNCTION ReturnObject( tcClass,tuPar1,tuPar2)
 LOCAL loReturn,lnCount,loError
 lnCount = PCOUNT()
 TRY 
  DO CASE 
   CASE lnCount = 1
    loReturn = CREATEOBJECT(tcClass,tuPar1)
   CASE lnCount = 2
    loReturn = CREATEOBJECT(tcClass,tuPar1,tuPar2)
   OTHERWISE 
    loReturn = CREATEOBJECT(tcClass)
  ENDCASE 
 CATCH TO loError
  loReturn = .f.
 ENDTRY 
 RETURN loReturn 
ENDFUNC 

Foxpro and Excel

It is very possible to automate spreadsheets from Visual Foxpro using VBA/ COM automation. Here I wil document the parts I need.

Connecting to Excel

oExcel = CreateObject("Excel.Application")
if vartype(oExcel) != "O"
  * could not instantiate Excel object
  * show an error message here
  return .F.
endif

I use my own library (JLIB) containing a ReturnObject function that fetches errors. Using that function my code looks like this:

PUBLIC goX && global Excel object

IF InitiateExcel()
 * Code here
ELSE
 ? [Error initiating Excel automation]
ENDIF 


PROCEDURE InitiateExcel
 goX = ReturnObject([excel.application])
 RETURN (VARTYPE(goX)=[O])
ENDPROC 

Open an existing spreadsheet

oWorkbook = oExcel.Application.Workbooks.Open("C:\temp\test.xls")

Create a new blank spreadsheet

oWorkbook = oExcel.Application.Workbooks.Add()

Create a new spreadsheet from a template

oWorkbook = oExcel.Application.Workbooks.Add("C:\temp\template.xlt")

Sheets collection/ objects

oWorkbook.sheets.count && returns the number of tabs/ sheets 
oSheet = oWorkbook.sheets(1) && get an object to control the sheet
? oSheet.name && show the sheet name

Tables

A table is called a ListObject in Excel/ Microsoft terminology

oSheet.ListObjects.count && gives the number of tables on the sheet
oTable = oSheet.ListObjects(1) && get an object to control the table
? oTable.ListRows.count && displays the number of data rows in the table
oTable.DataBodyRange.delete && deletes al rows. Error (crash) if there are no rows!

Working with table rows

Adding a table row needs several steps: 1. Add the row and keep the reference to it. 2. Add the column data 1 by 1:

oRow = oTable.ListRows.Add && you have a new row and a reference to it
oRow.Range(1) = Date(2020,7,6) && Add a date in the first column

Using named cells

? oSheet.Range("ProductionDate").value && show value of cell named ProductionDate
oSheet.Range("TotalPages").value = 13

Saving and closing

The save method is in the workbook object. And you can quit from the Excel application object:

oSheet.SaveAs([x:\reports\report.xlsx]) && Save
oExcel.Quit && quit Excel

The Quit only works on this Excel application you’ve initiated. So existing opened Excel sheets are untouched (remain opened).

Sources used

VFP: Create header files

Header files (.H) contain declarations and sometimes code. Software uses codes for specific actions or settings, but these codes are illegible for most humans. The header file for Microsoft Word 2016 contains more than 4.500 lines. It starts like this:

* WdMailSystem
#DEFINE wdNoMailSystem 0
#DEFINE wdMAPI 1
#DEFINE wdPowerTalk 2
#DEFINE wdMAPIandPowerTalk 3

* WdTemplateType
#DEFINE wdNormalTemplate 0
#DEFINE wdGlobalTemplate 1
#DEFINE wdAttachedTemplate 2

The header files are not included in the distributions, so you have to search for them yourself or create them if possible.

This program originates from an old Microsoft article. It creates a dialog where you can locate an .OLB file (Object Library). It then extracts all constants and gives you the ability to save them to your header file.

* Bron: https://web.archive.org/web/20110808014156/http://support.microsoft.com/kb/285396

****************START CODE****************
PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.SHOW
RETURN


****************FORM CODE****************
DEFINE CLASS form1 AS FORM

	HEIGHT = 445
	WIDTH = 567
	DOCREATE = .T.
	AUTOCENTER = .T.
	BORDERSTYLE = 1
	CAPTION = ".OLB Constants Extractor"
	MAXBUTTON = .F.
	MINBUTTON = .F.
	NAME = "Form1"

	ADD OBJECT txtolbfile AS TEXTBOX WITH ;
		HEIGHT = 27, ;
		LEFT = 65, ;
		READONLY = .T., ;
		TABINDEX = 2, ;
		TOP = 6, ;
		WIDTH = 458, ;
		NAME = "txtOLBFILE"

	ADD OBJECT label1 AS LABEL WITH ;
		AUTOSIZE = .T., ;
		CAPTION = ".\<OLB File:", ;
		HEIGHT = 17, ;
		LEFT = 4, ;
		TOP = 11, ;
		WIDTH = 55, ;
		TABINDEX = 1, ;
		NAME = "Label1"

	ADD OBJECT cmdsave AS COMMANDBUTTON WITH ;
		TOP = 411, ;
		LEFT = 394, ;
		HEIGHT = 27, ;
		WIDTH = 84, ;
		CAPTION = "\<Save to .h", ;
		ENABLED = .F., ;
		TABINDEX = 6, ;
		NAME = "cmdSAVE"

	ADD OBJECT cmdquit AS COMMANDBUTTON WITH ;
		TOP = 411, ;
		LEFT = 480, ;
		HEIGHT = 27, ;
		WIDTH = 84, ;
		CAPTION = "\<Quit", ;
		TABINDEX = 7, ;
		NAME = "cmdQUIT"

	ADD OBJECT edtconstants AS EDITBOX WITH ;
		HEIGHT = 347, ;
		LEFT = 6, ;
		READONLY = .T., ;
		TABINDEX = 4, ;
		TOP = 52, ;
		WIDTH = 558, ;
		NAME = "edtConstants"

	ADD OBJECT cmdgetfile AS COMMANDBUTTON WITH ;
		TOP = 6, ;
		LEFT = 533, ;
		HEIGHT = 27, ;
		WIDTH = 26, ;
		CAPTION = "...", ;
		TABINDEX = 3, ;
		NAME = "cmdGETFILE"

	ADD OBJECT cmdextract AS COMMANDBUTTON WITH ;
		TOP = 411, ;
		LEFT = 280, ;
		HEIGHT = 27, ;
		WIDTH = 110, ;
		CAPTION = "\<Extract Constants", ;
		ENABLED = .F., ;
		TABINDEX = 5, ;
		NAME = "cmdEXTRACT"


	PROCEDURE cmdsave.CLICK
		STRTOFILE(THISFORM.edtconstants.VALUE,PUTFILE([Header File], ;
			JUSTSTEM(THISFORM.txtolbfile.VALUE) + [.h],[.h]))
	ENDPROC


	PROCEDURE cmdquit.CLICK
		THISFORM.RELEASE
	ENDPROC


	PROCEDURE cmdgetfile.CLICK
		LOCAL lcOLBFile

		lcOLBFile = GETFILE([OLB],[OLB File],[Open])
		IF EMPTY(lcOLBFile)
			RETURN .F.
		ENDIF
		
		IF UPPER(RIGHT(lcOLBFile,3)) # [OLB]
			MESSAGEBOX([Invalid File],0,[])
			RETURN .F.
		ENDIF

		THISFORM.txtolbfile.VALUE = lcOLBFile
		THISFORM.cmdextract.ENABLED= .T.
	ENDPROC


	PROCEDURE cmdextract.CLICK
		WAIT WINDOW [Processing...] NOCLEAR NOWAIT
		LOCAL oTLB_INFO, oConstants, lcConstantsStr, Obj, member
		#DEFINE CRLF CHR(13) + CHR(10)

		oTLB_INFO = CREATEOBJECT([tli.typelibinfo])
		oTLB_INFO.ContainingFile = (THISFORM.txtolbfile.VALUE)

		oConstants = oTLB_INFO.Constants

		lcConstantsStr = []
		FOR EACH Obj IN oTLB_INFO.Constants
                        lcConstantsStr = lcConstantsStr + CRLF + "* " + Obj.Name + CRLF   
                        FOR EACH member IN Obj.Members
                            lcConstantsStr = lcConstantsStr + [#DEFINE ] + ;
                            member.NAME + [ ] + ;
                            TRANSFORM(member.VALUE) + CRLF
                        NEXT member
		NEXT Obj

		THISFORM.edtconstants.VALUE=lcConstantsStr
		THISFORM.cmdsave.ENABLED= .T.
		WAIT CLEAR
		WAIT WINDOW [Complete!] TIMEOUT 2
	ENDPROC

ENDDEFINE
****************END CODE****************

VFP: Check already running (NL)

Draait een programma al?

Door jasperdg op 16 Jul 2007

Het is soms niet handig als je een programma start terwijl dit al draait. Vooral bij programma’s die bewerkingen uitvoeren op databases is dit zeer ongewenst (indexeer programma’s etc). Hier een geschikte module die met Win32Api functies de actieve vensters uitleest en kijkt of een specifiek programma in de actieve titels voorkomt…

Met dank aan FoxStuff.

Programma IsRunning.prg :

 (0 als niet gevonden, anders het handle nummer)

* Generic routine to check if a given 
* application is running on the user's system.
* Parameter is all or part of the window's title.
LPARAMETERS tcTitle

DECLARE INTEGER GetActiveWindow IN Win32API
DECLARE INTEGER GetWindow IN Win32API ;
  INTEGER hWnd, INTEGER nType
DECLARE INTEGER GetWindowText IN Win32API ;
  INTEGER hWnd, STRING @cText, INTEGER nType

lhNext = GetActiveWindow() && current app's window
* iterate through the open windows
DO WHILE lhNext#0
 lcText = REPLICATE(CHR(0),80)
 GetWindowText(lhNext,@lcText,80) && get window title
 IF UPPER(ALLTRIM(tcTitle)) $ UPPER(lcText)
  * parameter text is present in window title
  RETURN lhNext
 ENDIF
 lhNext = GetWindow(lhNext,2) && next window
ENDDO
* required window not found
RETURN 0