The application provides information and working examples of the use of VB6 code for programing LibreOffice files, basically 'Calc' spreadsheets, as data sources.
The application comes with a Calc workbook to be used with the examples, back-up version of this and a template from which to create more such workbooks. The code hasn't been compiled so that the user is free to modify the code and the data in the Workbook to make their own experiments.
In this documentation, the VB code is in blue and comments are in green. The text in red are references to the 'Code examples' menu used in the VB6 application.
Unless otherwise stated in this documentation and the documentation in the files, the copyright of this package, and all its associated code, text and graphics, are held by Philip Bolt who has declared his rights as the author in accordance with the Copyright, Designs and Patents Act 1988.
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
When the package is run, the user is given the option of loading a workbook and the 'Code examples' menu is disabled until this is done.
When a file is loaded the 'Code examples' option is enabled so that the user can access the examples.
Click an option on one of the graphics below to see a description of the example concerned.
The workbook provided, 'samples.ods', has data ready to be used by the various illustrations and it's suggested that this isn't changed but used as a template for you to create your own data for experiments. For this purpose there is a version saved as a template, 'samples.ott'. In case of mistakes, there's also a back-up version of this, 'samples_bu.ods'
In order to preserve the sample data, the various illustrations DO NOT save the workbook. However, in actual use it would be necessary to do this on some occasions. e.g.in order to save a new style which has been created.
After some operations LO will prompt you to save the workbook as it's being closed. For the reasons given above, it's suggested that you don't do this.
Basics & Files
All programming must start by defining,
Creating a document [File handling: Create a file]
Dim loCalc as object | 'A document object |
Set loCalc = loDesk.loadComponentFromURL("private:factory/scalc", "_blank", 0, arg()) |
This will create an .ods document. Use "private:factory/swriter", for a Text, (odt), document.
Opening an existing document [File handling: Open]
Set loCalc = loDesk.loadComponentFromURL("file:///g:/programing/VB%20Projects/LOSStests/test1.ods", "_blank", 0, arg())
Note the structure of the URL, particularly the handling of the space. There is a function, ConvertToUrl, which will convert a standard file path into this form.
Save a document [File handling: Save]
Call loCalc.storeToURL("file:///g:/programing/VB%20Projects/LOSStests/test_bk2.ods", arg())
Close a document [File handling: Close]
loCalc.Close True
There are some situations which I don't presently understand where, whilst 'Close' works, it also generates an error. For the time being these are dealt with by an error trap On Error Resume Next.
Once an object name is no longer in use its handle should be released, e.g.
Set loCalc = nothing: Set loDesk = nothing: Set loSM = nothing, etc.
Using arguments
The arg() array in the previous statements can be loaded with actual arguments, most easily by the use of a MakePropertyValue function.
Function MakePropertyValue(cName, uValue) As Object | |
Dim loStruct As Object | |
Set loStruct = loSM.Bridge_GetStruct( _ "com.sun.star.beans.PropertyValue") | 'loSM is assumed to exist with at least modular scope |
loStruct.Name = cName | |
loStruct.Value = uValue | |
Set MakePropertyValue = loStruct | |
Set loStruct=nothing | 'As an alternative, loStruct could be given modular or global scope. |
End Function |
This is then used as follows,
Dim OpenParam(1) As Object | 'Parameters to open the document |
Dim SaveParam(1) As Object | 'Parameters to save the document |
Set OpenParam(0) = MakePropertyValue("Hidden", True) | 'Open the file as hidden |
… | |
Set loDoc = loDesk.loadComponentFromURL("file:///G:/Programing/ _ VB%20Projects/LOSStests/test_bk.ods", "_blank", 0, OpenParam()) | |
Set SaveParam(0) = MakePropertyValue ("FilterName", "writer_pdf_Export") | 'Save as a PDF |
… | |
Call loCalc.storeToURL("file:///G:/Programing/VB%20Projects/LOSStests/test1.pdf", SaveParam()) |
The possible names and values of parameters are,
Name | Value |
---|---|
Hidden | True/False |
FilterName | writer_pdf_Export , Text - txt - csv (StarCalc), MS Excel 2007 XML |
Overwrite | True/False |
ReadOnly | True/False |
Password | Secret/ Clear? |
Deleting a file
A LibreOffice file is no different to any other as far as deleting it in VB6 is concerned so the usual 'Kill' command can be used.
Multiple files [File handling: Open several files, Close one of several files, Close a group of files]
The previous code is written on the basis of there only being a single workbook open at a time. 'open_wb' checks for any open file, saves it and closes it before opening the requested file.
However, there are many situations where it'll be necessary to have several open work books and the routines 'open_multi_wb', 'close_file' and 'close_down_multi' deal with that situation.
The main problem is knowing which file you're actually targeting at any given time as LibreOffice doesn't have any simple construct such as file.Name or file.Id to identify a workbook. The method used in the examples is based on the file 'Title' property using,
props = loCalc.getDocumentProperties() | |
i_d = props.title |
where the title has been made the workbook file name. The 'Title' property can be set programmatically using 'props', (props.title = [file name]), but more easily by giving each work book its basic file name as a Title via LibreOffice, (File, Properties)
Spreadsheets
Data in Cells [Data manipulation: Read/Write data]
Cells on a variety of sheets can be processed by referring to the cell via its position and sheet. Positions are defined by Cartesian coords, i.e. starting at zero and listing X, (column), followed by Y, (row).
Dim cel As Object | |
Set cel = loSheets(sht).getCellByPosition(c, r) | 'Define the required Cell. |
Different code is required to manipulate the data depending on its nature.
If IsNumeric(dta) Then | 'If the data can be recognised as a number, then |
cel.Value = dta | 'store as numeric, NB a string of digits will generate TRUE! |
Else | 'Otherwise, |
cel.String = dta | 'store it as a string |
End If |
Similar structures are used to read the data in a cell.
Set cel = loSheets(sht).getCellByPosition(c, r) | |
typ = cel.getType() | |
Select Case typ | |
Case 0 | 'is EMPTY |
Rcell1 = "" | |
Case 1 | 'is NUMERIC |
Rcell1 = cel.Value | |
Case 2 | 'is a STRING |
Rcell1 = cel.String | |
Case 3 | 'is a FORMULA |
Rcell1 = cel.Formula | 'Essentially, this will be a string. |
End Select |
If a number of cells are going to be processed on the same sheet, that sheet can be defined as the Active sheet and the above functions modified to reduce the parameters to just the row and column.
Dim cel, acts As Object | |
Set acts = loCalc.CurrentController.ActiveSheet | 'Define the ActiveSheet object & |
et cel = acts.getCellByPosition(c, r) | 'use it to define the required Cell. |
Ranges [Data selection: Make Active Sheet, Range test]
Dim loRng, acts As Object | |
loCalc.getcurrentController.setActiveSheet loSheets(0) | 'Make a sheet active and |
Set acts = loCalc.CurrentController.ActiveSheet) | 'define the ActiveSheet object. |
A range of cells can then be defined on the Active sheet.
Set loRng = acts.getCellRangeByPosition(2, 1, 6, 13) | '(c1, r1), (c2, r2); i.e. C2 to G14 |
Move or copy a range [Data selection: Move range, Copy Range]
A cell range address and a cell address object are created and the moveRange or copyRange commands used. NB. A range or cell address is NOT the same thing as a range or cell object.
rng = CreateCellRangeAddress(sht1, sc, sr, ec, er) | 'Define the data range to be moved and |
cel = CreateCellAddress(sht2, c, r) | 'the address of the TL corner cell of the destination. |
… | |
sht1.moveRange cel, rng | 'This will MOVE the data. |
sht1.copyRange cel, rng | 'This will COPY the data. |
The destination for the change of data can be on a different sheet or probably into a different workbook although I've never checked the latter.
Cell properties [Formating: Get Cell properties, Set Cell properties, Set Cell colour]
A cell object can be defined as shown in the previous section and a range of properties accessed by the cel.Cell[property] command. E.g. cel.CellBackColor = RGB(255, 0, 0)
The list below is NOT exclusive.
Command | Values |
---|---|
CellBackColor | RGB(actually B,G,R) Note non-standard order |
CharColor | (as above) |
CharFontName | Any installed font name |
CharWeight | % values, 100 = normal, 150 = Bold, 50 = Light, etc. |
CharHeight | Point size |
CharPosture | Italic = 2 * |
CharUnderline | Single underline = 1 † |
CharOverline | Single overline = 1 ‡ |
Value | Code |
---|---|
0 | NONE |
1 | OBLIQUE |
2 | ITALIC |
3 | DONTKNOW |
4 | REVERSE OBLIQUE |
5 | REVERSE ITALIC |
Value | Code |
---|---|
0 | NONE, no underlining |
1 | SINGLE, a single solid line |
2 | DOUBLE, a double solid line. |
3 | DOTTED, a single dotted line. |
4 | DONTKNOW |
5 | DASH, a single dashed line |
6 | LONGDASH, with long dashes. |
7 | DASHDOT, a dash and dot sequence. |
8 | DASHDOTDOT, a dash, dot, dot sequence. |
9 | SMALLWAVE, a single small wave. |
10 | WAVE, a single wave. |
11 | DOUBLEWAVE, a double wave. |
12 | BOLD, a bold line. |
13 | BOLDDOTTED, bold dots. |
14 | BOLDDASH, bold dashes. |
15 | BOLDLONGDASH, long bold dashes. |
16 | BOLDDASHDOT, a dash and dot sequence in bold. |
17 | BOLDDASHDOTDOT, a dash, dot, dot sequence in bold. |
18 | BOLDWAVE, a bold wave. |
Styling
Justification [Formating: Right justify]
An individual Cell can have its contents horizontally justified by use of 'HoriJustify'. cel.HoriJustify = 3 and similarly for other alignments.
Parm. | Operation | Value |
---|---|---|
STANDARD | Default alignment is used (left for numbers, right for text) | 0 |
LEFT | Contents are aligned to the left edge of the cell | 1 |
CENTER | contents are horizontally centred | 2 |
RIGHT | Contents are aligned to the right edge of the cell | 3 |
BLOCK | Contents are justified to the cell width | 4 |
REPEAT | ontents are repeated to fill the cell | 5 |
A similar operation exists for vertical justification. cel.VertJustify = [value]
Parm. | Operation | Value |
---|---|---|
STANDARD | Default alignment is used | 0 |
TOP | Contents are aligned with the upper edge of the cell | 1 |
CENTER | Contents are aligned to the vertical middle of the cell | 2 |
BOTTOM | Contents are aligned to the lower edge of the cell | 3 |
However, a major point of LO design is the separation of content and display and the recommended method of setting a cell's display properties is via a pre-defined Style rather than individual styling commands as shown above.
There are different sets of styles for Pages, (i.e. for Text documents), and Cells, (i.e. for Calc workbooks), but they are both accessed in a similar way.
loCalc.getcurrentController.setActiveSheet loSheets(0) | 'Make a sheet active and |
Set acts = loCalc.CurrentController.ActiveSheet | 'define the ActiveSheet object |
… | |
Set StyleFamilies = loCalc.StyleFamilies | 'Store the Style Families, |
Set PageStyles = StyleFamilies.getByName("PageStyles") | 'the Page styles array and |
Set CellStyles = StyleFamilies.getByName("CellStyles") | 'the Cell styles array. |
… | |
Set cel = acts.getCellByPosition(2, 5) | 'Select a cell and |
cel.CellStyle = [Style name as string] | 'apply the named style. |
VB can also be used to create a custom style but it's probably easier to do this via LO unless the workbook concerned is being created at runtime. All Calc workbooks have a Default Style which can be used to create a custom style.
Set StyleFamilies = loCalc.StyleFamilies | 'Store the Style Families, |
Set PageStyles = StyleFamilies.getByName("PageStyles") | 'the Page styles array and |
Set CellStyles = StyleFamilies.getByName("CellStyles") | 'the Cell styles array. |
… | |
Set cStyle = CellStyles.getByName("Default") | 'Set the Style object to the DEFAULT style and |
cStyle.Name = [New Style name as string] | 'store it with the new name. |
It is necessary to save the workbook to preserve the new style: this isn't done in the examples.
Once a Style has been defined, its properties can be set, either individually or by use of a Variant array. See the 'Cell properties' section for the list of possible codes.
If parm(0) <> "" Then cStyle.CharColor = parm(0) | 'RGB notation, actually, B, G, R |
If parm(1) <> "" Then cStyle.CellBackColor = parm(1) | 'RGB notation, actually, B, G, R |
If parm(2) <> "" Then cStyle.CharFontName = parm(2) | 'As string |
If parm(3) <> "" Then cStyle.CharHeight = parm(3) | 'Point size, as integer. |
If parm(4) <> "" Then cStyle.CharWeight = parm(4) | '% value, 100 = normal, etc. |
If parm(5) <> "" Then cStyle.CharPosture = parm(5) | 'Posture code. |
If parm(6) <> "" Then cStyle.CharUnderline = parm(6) | 'Value for underline style, 0 if none. |
If parm(7) <> "" Then cStyle.CharOverline = parm(7) | 'Value for overline style, 0 if none. |
The 'If' statements are required to leave intact those properties of 'Default' which are required for the new Style.
Printing
Using Call loCalc.Print will activate the VB Print method so you need to use CallByName loCalc, "print", vbMethod, arg()
The arguments, arg(), are used both to set up the printer parameters and define the area/s to be printed.
Parameter name | Value |
---|---|
Name | The name of printer as displayed in the print dialogue |
PaperOrientation | Specifies the paper orientation (com.sun.star.view.PaperOrientation.PORTRAIT value for portrait format, com.sun.star.view.PaperOrientation.LANDSCAPE for landscape format) |
PaperFormat | The paper format (for example, com.sun.star.view.PaperFormat.A4 for DIN A4 or com.sun.star.view.PaperFormat.Letter for US letters) |
PaperSize | The paper size in hundredths of a millimeter. See "LibreOffice 3-4 Basic Programmer's Guide", p.65 |
CopyCount | The number of copies to be printed |
FileName | Prints the document in the specified file |
Collate | Boolean: collate the pages of the copies |
Sort | Boolean: sorts the pages if CopyCount > 1 |
Pages | String: contains the list of the pages to be printed (syntax as specified in print dialogue) |
ToPoint | String: Range/Cell value in alpha-numeric format, (B4:E8). A range object, (robj), can be used to find this via robj .AbsoluteName |
Wait | Boolean: if set to True the print method will return after the job is stored on the waiting list for the printer. Use this option if you want to close the document after print |
Print area [Data selection: Set Print area (1), Set Print area (2). Get Range sheet name]
This was more difficult than originally thought. It's easy to define and select a range and I thought that by naming the range, it would be easy to select this as a print area but, despite what is stated in the LO documentation concerning named ranges, such a range does NOT act as a print area. The basic problem is that a print area needs to be defined in terms of a range name, rather than a range object.
I use one of two approaches.
Define the area in the actual routine.
Set rng = sht.getCellRangeByPosition(c1, r1, c2, r2) | 'Define the area. |
addr = rng.getRangeAddress() | 'Store the range address and |
sht.setPrintAreas (Array(addr)) | 'use it to set the Print area. |
or use a pre-defined range and get the sheet name from its AbsoluteName via a custom function, get_rng_sht().
sht = get_rng_sht(rng) | 'Get the sheet object from the range, |
addr = rng.getRangeAddress() | 'store the range address and |
sht.setPrintAreas (Array(addr)) | 'use it to set the Print area. |
Show or Hide rows or columns [Modify sheets: Hide columns, Hide rows]
rng.Columns.IsVisible = flg
rng, a range that defines the columns, flg, a Boolean that sets the action. True = Show, False = Hide.
Similarly for rows using Rows.IsVisible.
Insert or Delete rows or columns [Modify sheets: Insert rows/columns, Delete rows/columns]
sht.Rows.insertByIndex s, n
The parameters are, sht, the sheet concerned, s, the start row, n, the number of rows to be added. Similarly for deleting rows, (deleteByIndex), and the equivalent column operations.
Window routines
Resize [Window size: Set the Window size]
In LO the window is accessed via a Frame object defined on the Desktop. It's possible to set the width, height and TL corner of the Window object.
loFrame = loDesk.getCurrentFrame() | 'Define the Frame object, |
loWindow = loFrame.getContainerWindow() | 'the Window object and, hence |
loRect = loWindow.getPosSize() | 'the position and size of the window rectangle. |
Dim intHeight As Integer | |
Dim intWidth As Integer | |
Dim intXPos As Integer | |
Dim intYPos As Integer |
intXPos = loRect.X | 'Top-left X-coord, |
intYPos = loRect.Y | 'Top-left Y-coord. |
The Window size can then be set by the setPosSize command.
loWindow.setPosSize intXPos, intYPos, intWidth, intHeight, 15 The final parameter, 15 above, is a flag which controls which parameters are to be used by the command.
Parm. | Operation | Value |
---|---|---|
X | Flags the x-coordinate | 1 |
Y | Flags the y-coordinate | 2 |
WIDTH | Flags the width | 4 |
HEIGHT | Flags the height | 8 |
POS | Flags the x- and y-coordinates | 3 |
SIZE | Flags the width and heigh | 12 |
POSSIZE | Flags the x- and y-coordinate, width and height | 15 |
Window size [Window size: Read the Window size]
This is most useful during application design but might have other uses. The getPosSize command is used to read the parameters described above.
wv = loRect.Width | |
ihv = loRect.Height | |
txv = loRect.X | |
tyv = loRect.Y | |
MsgBox wv & ", " & hv & ", " & txv & ", " & tyv | 'Display of values for design use. |
Page zoom [Window size: zoom]
As far as I've been able to find out, it's NOT possible to set an individual zoom value for each sheet in a workbook: i.e. whatever is set applies to all sheets in the book. The commands are ZoomType and ZoomValue.
loCalc.CurrentController.ZoomType = tp | |
loCalc.CurrentController.ZoomValue = vl |
Parmameter | Operation | Value |
---|---|---|
OPTIMAL | The page content width (excluding margins) at the current selection is fit into the view | 0 |
PAGE_WIDTH | The page width at the current selection is fit into the view | 1 |
ENTIRE_PAGE | A complete page of the document is fitted into the view | 2 |
BY_VALUE | The zoom is relative and is to be set via the property ZoomValue | 3 |
PAGE_WIDTH_EXACT | The page width at the current selection is fit into the view, with the view ends exactly at the end of the page. | 4 |
ZoomValue takes a parameter as a % which fixes size of the zoom when ZoomType is set to BY_VALUE.
Fill [Modify sheets; Fill]
LO has far more versatile 'Fill' commands than Excel. The main purpose of this command is to generate data sets rather than just copying a line or column of data, although this can still be done.
The most useful command is fillSeries(Direction, Fill mode, Date mode, Step, End).
Parameter | Operation |
---|---|
Direction | Specifies the direction to fill the rows/columns of the range |
Fill mode | Specifies the type of the series |
Date mode | Specifies the calculation mode for date values |
Step | Contains the value used to increase/decrease the series values |
End | Contains the threshold value at which the calculation of the current series stops |
'Direction' Parameter | Operation | Value * |
---|---|---|
TO_BOTTOM, (Fill DOWN) | Specifies that rows are filled from top to bottom | 0 |
TO_RIGHT, (Fill RIGHT) | Specifies that columns are filled from left to right | 1 |
TO_TOP, (Fill UP) | Specifies that rows are filled from bottom to top | 2 |
TO_LEFT, (Fill LEFT) | Specifies that columns are filled from right to left | 3 |
'Fill mode' Parameter | Operation | Value * |
---|---|---|
SIMPLE † | Specifies a constant series | 0 |
LINEAR | Specifies an arithmetic series | 1 |
GROWTH | Specifies a geometric series | 2 |
DATE | Specifies an arithmetic series for date values | 3 |
AUTO | Specifies the use of a user-defined list | 4 |
† This is the value to use if data is to be copied unchanged as in the Excel 'Fill left', etc.
'Date mode' Parameter | Operation | Value * |
---|---|---|
FILL_DATE_DAY | For every new value a single day is added | 0 |
FILL_DATE_WEEKDAY | For every new value a single day is added, but Saturdays and Sundays are skipped | 1 |
FILL_DATE_MONTH | For every new value one month is added (day keeps unchanged) | 2 |
FILL_DATE_YEAR | For every new value one year is added (day and month keep unchanged) | 3 |
* Using LO BASIC, these values are set by com.sun.star.sheet.FillDirection.TO_RIGHT etc, but I haven't got this to work with VB6, so the actual value needs to be used.
If SIMPLE mode is used, any formulae in the selected data will be correctly updated as they are copied provided that a zero step value is used. In any other case, the formula is not copied and the initial value given by the formula is incremented by the step.
If a calculated value exceeds the 'End' value, an error is generated so it's usual to use very large values to avoid this.
As an example, the following command will produce the same result as an Excel 'Fill Down' instruction.
Set rnge = loSheets(1).getCellRangeByPosition(2, 0, 2, 10) | 'Select the cells, C1:C11 in order to copy the data in C1 down into the rest of the range. |
rnge.fillSeries 0, 0, 0, 0, &H7FFFFF | 'Set the 'Direction' to TO_BOTTOM, 'Fill mode' to SIMPLE, 'Date mode' to FILL_DATE_DAY as a dummy, set 'Step' to 0 to keep the data unchanged and set 'End' so large as not to be exceeded. |
There is also a simpler fill command, fillAuto(Direction, Count)
The direction values are the same as listed above for fillSeries.
The method increments the number by 1 while moving to the right or bottom, and decrements the number by 1 while moving to the left or top. If the number is formatted as a time or date, incrementing by 1 adds one day. The 'Count' parameter controls the number of rows or columns to move before entering a new value. A value of 1, therefore, fills every cell in the range.
I've never actually used this. Whilst it seems useful for generating series of data, I'm more interested in mimicking the Excel 'Fill' command as I'm in the process of translating a lot of VB6 projects based on Excel to use LO Calc.
Sorting
So far, (4/6/20), I've not been able to write a VB6 routine which utilises the LO BASIC sorting commands. The ones I've written don't generate an error when run but they also don't work: nothing happens!
What I'm using is a VB6 Quick sort routine which transfers the data to an array for sorting and then re-inserts this back onto the sheet. Such sorting can be done by using the data on the sheet directly but this is even slower than moving it in and out of an array.
There are two sort routines in the package: one is written for a single key search for data which starts on row 1, (coded zero); the other for a multiple field sort for a block of data anywhere on a sheet. The former isn't actually needed as the multiple field version can be used with a single key field but the single key version uses less code.
I used to use a Shell sort but find the Quick sort to be a little faster. Incidentally, I, (along with many others), used to refer to this as the 'Shell-Metzner' sort but I recently found a note from Marlene Metzner on the NIST website [1] in which she denies making any improvement to Don Shell's algorithm; claiming all she did was to code it in Fortran.
Recording macros
The starting place for creating VB code for Excel workbooks is to record a macro of the activity concerned. Very often the VBA macro code will run 'as is' in VB6 or after a limited amount of editing.
The same isn't true for LO BASIC.
LO has an option to set macros to run VBA code.
"After loading the VBA code, LibreOffice inserts the statement Option VBASupport 1 in every Basic module to enable a limited support for VBA statements, functions and objects." [1]
The important word is "limited". In very few cases will the code actually work: why should it?
Porting Excel/VBA to Calc/StarBasic states that,
" While the same technique can be used in Calc/SB, the experience to date in using the technique has not been very successful. The code generated by the macro recorder is based on interacting with the spreadsheet versus recording the resultant manipulations of the spreadsheet object model. It is possible to generalize the recorded code. However, it provides little insight into use of the spreadsheet object model. An alternative to using the native macro recorder feature in Calc is to download the Calc macro recorder from http://ooomacros.org/user.php written by Paolo Mantovani. Paolo's macro recorder creates a macro that primarily uses references to the Calc objects rather than the more cryptic dispatcher calls. The macro guide at http://www.math.umd.edu/~dcarrera/openoffice/docs/ contains an excellent description of how to use the macro recorder and arrange your macros into libraries." [2]
The links in the above passage are no longer active but I tracked down a source of Mantovani's code. However, it's now sixteen years old and it made no difference to what was recorded.
However, recording a macro can give some guidance if you remember the above points and that VB is referencing objects at one step removed when compared to LO BASIC.
References
Much of the information in this document is already in the public domain and a list of references is given below. NB with the exception of the first and second items, the BASIC these refer to is LO BASIC which is LibreOffice's version of MS VBA.