Wide Excel Automation(Updated: 2007.08.24 01:47:10 PM)
Namespace: VFP
Edit -Find- Recent Changes Categories Road Map [A-F] [G-P] [Q-Z] New Topic Home Search:For clarity's sake, the full object hierarchy is used for each command. Using a 'With oExcel' will tend to make your code more readable.
Connecting to Excel
We'll assume throughout this page that you named your Excel object oExcel and your workbook oWorkbook.
oExcel = CreateObject("Excel.Application")
if vartype(oExcel) != "O"
* could not instantiate Excel object
* show an error message here
return .F.
endif
--------------------------------------------------------------------------------
Constants for Excel
Excel Constants documents the constants that Excel's macros use (xlThis, xlThat etc)
2007/08/30 Mike Mohr - Excel 2003 Constants MSDN Library
2007/08/30 Mike Mohr - Excel 2007 (office 12) Constants MSDN Library
--------------------------------------------------------------------------------
Opening an existing XLS
oWorkbook = oExcel.Application.Workbooks.Open("C:\temp\test.xls")
Creating a blank workbook:
oWorkbook = oExcel.Application.Workbooks.Add()
Creating a new workbook based on a template:
oWorkbook = oExcel.Application.Workbooks.Add("C:\temp\template.xlt")
--------------------------------------------------------------------------------
You can turn off Excel's alerts with the DisplayAlerts property: when set to False, Excel automatically chooses the default answer to any message:
oExcel.DisplayAlerts = .F.
oWorkbook.Close() && Unsaved changes will be discarded
oExcel.DisplayAlerts = .T.
--------------------------------------------------------------------------------
Saving a workbook as an Excel 97/2003 spreadsheet from Excel 2007
Using SaveAs while automating Excel 2007 creates a 2007 style workbook with an XLS extension (regardless of compatibility settings) unless the file format is specified:
if val(oExcel.Version) > 11
oWorkbook.SaveAs("C:\temp\foobar.xls", 56) && xlExcel8
else
oWorkbook.SaveAs("C:\temp\foobar.xls")
endif
--------------------------------------------------------------------------------
Controlling visibility
If the Excel window is not visible it is harder for the user to interact with Excel. This makes it slightly safer for your automation as the user is less likely to issue commands in the middle of your automation.
oExcel.visible = .T.
oExcel.visible = .F.
--------------------------------------------------------------------------------
Controlling Interaction
Also, if it is preferred that Excel be seen during automation set these two properties to .F.
oExcel.Application.UserControl=.F.
oExcel.Application.Interactive=.F.
After completing automation, return their value to .T. to allow the user to start interaction
oExcel.Application.UserControl=.T.
oExcel.Application.Interactive=.T.
--------------------------------------------------------------------------------
Storing data to a cell
oExcel.Range("b2").Value = "Hello world"
--------------------------------------------------------------------------------
Set the font color and style
oExcel.Range("B6").font.bold = .t.
oExcel.Range("B6").font.colorindex = 3 && red
or
oExcel.Range("B6").Select()
oExcel.Selection.font.colorindex = 3 && red
oExcel.Selection.font.bold = .t.
-- David Fung
--------------------------------------------------------------------------------
Getting data into Excel
Assuming your table is selected. First the easy way. You can make a new file in an old Excel format which all version should be able to read.
copy to c:\mytable.xls type xl5
Or if you have a pre-formatted template (.XLS or .XLT) that you want to paste into. Note that this method will not handle Memo fields.
_VFP.DataToClip(,,3) && current table onto the clipboard, delimited with tab
oExcel.Range("A1").Select
oExcel.ActiveSheet.Paste() && from clipboard. since delimited with tab, store data into columns
--------------------------------------------------------------------------------
Selecting a range using the Cells collection
oExcel.Range(oExcel.cells(1, 1), oExcel.Cells(3.3)).Select
--------------------------------------------------------------------------------
Resize all columns
oExcel.ActiveSheet.UsedRange.EntireColumn.Autofit
--------------------------------------------------------------------------------
Insert two rows before the first row
oExcel.Rows("1:2").Insert(-4121) && xlDown
-- David Fung
--------------------------------------------------------------------------------
Closing Excel
You'll still need to handle closing questions like saving changes and file format changes. And you'll need to release your oExcel object
oExcel.quit()
--------------------------------------------------------------------------------
Closing Excel with all changes discarded - no question asked
oExcel.DisplayAlerts = .F.
oExcel.ActiveWindow.Close(.f.) && assuming only one active window
oExcel.quit()
-- David Fung
--------------------------------------------------------------------------------
Iterate through Excel's Worksheets
For i=1 To oExcel.ActiveWorkbook.Sheets.Count
? oExcel.ActiveWorkbook.Sheets(i).Name
Endfor
--------------------------------------------------------------------------------
Finding text in Excel
Searching for "Tax"
oExcel.Range("A1:H250").Find("Tax").Select && simple default search
* Alternately
* Range.Find(What, After, LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)
oExcel.Range("A1:H250").Find("Tax", oExcel.ActiveCell, -4123, 1).Select
Range("A1:H250") specifies that we're searching columns A to H (inclusive) and rows 1-250.
oExcel.ActiveCell is where to start searching, and -4123 is the constant for xlFormulas. I theorize that this means 'if there is a formula, search its code rather than its output.' 1 is the constant for xlWhole, meaning match against all the text in the cell. You could swap in 2 to get partial matches.
-- Tom Cerul
You have to be careful when specifying the extra parameters to Find as they persist between searches, as specified in the Excel documentation:
The settings for LookIn, LookAt, SearchOrder, and MatchByte are saved each time you use this method. If you don't specify values for these arguments the next time you call the method, the saved values are used. Setting these arguments changes the settings in the Find dialog box, and changing the settings in the Find dialog box changes the saved values that are used if you omit the arguments. To avoid problems, set these arguments explicitly each time you use this method.
-- Stuart Dunkeld
--------------------------------------------------------------------------------
Developing new code
Sometimes the easiest way to figure out how to code an automation routine is this: open Excel, tell it to record a macro, do the task you want to automate. Stop the recording and look at the code that it generated.
--------------------------------------------------------------------------------
Putting it all together, a runnable example
First, COPY TO all fields (or some) in Excel Format
#define xlLastCell 11
#define xlMaximized -4137
#define xlRangeAutoformatClassic2 2
#define xlPortrait 1
use MyTable && or SELECT * INTO MyCursor
cFileName = "MyXLSFile" && or whatever, including path
*copy to (cFileName) fields (cFields) TYPE xls
copy to (cFileName) TYPE xls
* then open excel and make the data look good, like this
oExcel = CreateObject("Excel.Application")
if vartype(oExcel) != "O"
* could not instantiate Excel object
* show an error message here
return .F.
endif
* make excel visible during development
*oExcel.visible = .T.
* open the workbook you just created
oExcel.SheetsInNewWorkBook = 1
oWorkbook = oExcel.Workbooks.Open(cFileName)
* rename the Sheet to whatever you like
oActiveSheet = oExcel.ActiveSheet
oActiveSheet.Name = "MyData"
oExcelApp = oExcel.Application
oExcelApp.WindowState = xlMaximized
* find address of last occupied cell
lcLastCell = oExcel.ActiveCell.SpecialCells(xlLastCell).Address()
* resize all columns
lnMarker1 = at("$",lcLastCell,1) && i.e. 1 when lcLastCell = "$AF$105"
lnMarker2 = at("$",lcLastCell,2) && i.e. 4 when lcLastCell = "$AF$105"
lnStartPos = lnMarker1 + 1
lnStrLen = lnMarker2 - lnStartPos
oExcel.Columns("A:" + substr ;
(lcLastCell,lnStartPos,lnStrLen)).EntireColumn.AutoFit
* you can even add a nice autoformat
oExcel.Range("A" + alltrim(str(nTOPBLANKROWS+1)) + ":" + lcLastCell).Select
oExcel.Selection.AutoFormat(xlRangeAutoformatClassic2,.t.,.t.,.t.,.t.,.t.,.t.)
* set Excel Print Area
oActiveSheet.PageSetup.PrintArea = "$A$1:" + lcLastCell
* define printed page footer
With loActiveSheet.PageSetup
*.LeftHeader = ""
*.CenterHeader = ""
*.RightHeader = ""
.LeftFooter = "&BMy Footer goes here&B"
.CenterFooter = "&D"
.RightFooter = "Page &P"
*.PrintHeadings = .F.
.PrintGridlines = .F.
.CenterHorizontally = .T.
.CenterVertically = .F.
.Orientation = xlPortrait
endwith
* save Excel file in new Excel format (COPY TO XLS uses old format)
oWorkbook.Save()
* display finished product to the user
oExcel.visible = .T.
-- Alex Feldstein
--------------------------------------------------------------------------------
Sometimes the Last Cell is not up-to-date after deleting a row in Excel,
Calling ActiveSheet.UsedRange after deleting a row will keep Last Cell
up-to-date.
loExcel = createobject('Excel.Application')
loExcel.Workbooks.Open(tcFile)
loExcel.Rows("1").Delete(xlUp)
lnLastRowIncorrect = loExcel.Cells.SpecialCells(xlCellTypeLastCell).Row
loExcel.ActiveSheet.UsedRange && add this line
lnLastRowCorrect = loExcel.Cells.SpecialCells(xlCellTypeLastCell).Row
-- David Fung
Having worked with excel a lot a few notes:
Office applications are not as much backward compatible as VFP is. For that reason I suggest not to use VFP's direct Excel related commands like:
copy to myExcel type xls && or xl5,xl8...
import ... type xl8
etc. These commands are version dependant directly within VFP themselve. You immediately lose data with these commands.
-The number of rows you can copy is limited for example (VFP5 copied 16384 max while VFP9 copies 65536 max, but as new Excel versions come into market those limits are not sufficient anymore).
-Memo fields are immediately dropped as with any 'copy to' command
Data To Clip() - Do not use Data To Clip() for Excel transfers.
-You lose Memo fields but worse you get a transform() version of memo fields.
-You're limited on rowcount that you can transfer with this method. There is no exact number and sounds like related with Excel version + available memory. It's possible you end with much fewer rows than you could transfer using 'copy to'. Likely you'd get unpredictable exceptions.
Instead you can use:
Copy to myExcel.xls type fox2x && actually creating a dBaseIII compatible file. Excel recognizes internally
Copy to myExcel.csv type csv && CSV files are natively recognized
Both fail to transfer memo fields and CSV might have problems with datatypes converted correctly (mostly with date fields) but in general are better than Data To Clip() and 'copy ... type xls'.
Similar to Data To Clip() you can copy to a tab delimited file, read it into clipboard with FileToStr() and pasteSpecial() in Excel. Works better than Data To Clip() but it again falls short of transferring memo fields.
Excel (especially newer versions) also recognizes XML and HTM ( table tags ).
My best preferance is to transfer data using ADO instead. Passing with ADO uses Excel's own VBA commands to 'paste' the data. Here is a sample sending data to Excel and putting data starting at A10:
LOCAL oExcel
oExcel = Createobject("Excel.Application")
With oExcel
.WorkBooks.Add
.Visible = .T.
VFP2Excel(_samples+'data\testdata.dbc','select * from employee',.ActiveSheet.Range('A10'))
Endwith
function VFP2Excel
lparameters tcDataSource, tcSQL, toRange
Local loConn As AdoDB.Connection, ;
loRS As AdoDB.Recordset,;
ix
loConn = Createobject("Adodb.connection")
loConn.ConnectionString = "Provider=VFPOLEDB;Data Source="+m.tcDataSource
loConn.Open()
loRS = loConn.Execute(m.tcSQL)
FOR ix=1 TO loRS.Fields.Count
toRange.Offset(0,m.ix-1).Value = PROPER(loRs.Fields(m.ix-1).Name)
toRange.Offset(0,m.ix-1).Font.Bold = .t.
ENDFOR
toRange.Offset(1,0).CopyFromRecordSet( loRS )
loRs.Close
loConn.Close
Note that .Visible = .T. is very early in code just after adding the workbook. Having that "later after you're done with populating cells males things faster" is a myth. Surprisingly having it early in code makes your code faster in many cases.
My suggestions:
Working with Excel means you're doing COM calls using VBA which by nature is slow. Therefore, whenever possible, do things at VFP side and call Excel automation commands as few as you can. ie:
Instead of this:
for ix = 1 to 5000
for jx=1 to 10
.Cells(m.ix,m.jx).Value = m.ix*100+m.jx
endfor
endfor
Do this:
dimension aExcelData[5000,10]
for ix = 1 to 5000
for jx=1 to 10
aExcelData[m.ix,m.jx] = m.ix*100+m.jx
endfor
endfor
WITH oExcel.ActiveWorkBook.ActiveSheet
.Range(.Cells(1,1), .Cells(5000,10)).Value = GetArrayRef('aExcelData')
endwith
PROCEDURE GetArrayRef(tcArrayName)
RETURN @&tcArrayName
Above code also shows another alternative to transferring data to Excel via array instead of pasting.
Cetin Basoz
--------------------------------------------------------------------------------
Contributors: Alex Feldstein Tom Cerul Stuart Dunkeld David Fung Cetin Basoz
--------------------------------------------------------------------------------
Category Code Samples Category Automation
Edit -Find- Recent Changes Categories Road Map [A-F] [G-P] [Q-Z] New Topic Home Search:[URL unfurl="true"]http://fox.wikis.com[/URL] - a low-impedance, fat-free Visual Foxpro site. - 2009.06.29 05:15:03 AM EST