How do I make vba code compatible with libre office - excel

I have recently migrated to pclinuxos from windows and seem to like it. The only problem I am facing is that libreoffice, the default spreadsheet package is not compatible with excel macros. Below is the vba code I have:
Option VBASupport
Sub DeleteToLeft()
Selection.SpecialCells(xlBlanks).Delete shift:=xlToLeft
End Sub
Function SinceLastWash()
Application.Volatile
WashCount = 0
WearCount = 0
CurrentRow = Application.ThisCell.Row
For i = 3 To 35
If Range(Cells(CurrentRow, i), Cells(CurrentRow, i)).Value = "a" Then
WearCount = WearCount + 1
End If
If Range(Cells(CurrentRow, i), Cells(CurrentRow, i)).Value = "q" Then
WashCount = WashCount + 1
WearCount = 0
End If
Next i
SinceLastWash = WearCount
End Function
Function testhis()
testhis = Application.ThisCell.Row
End Function
Is there a way to convert this code to make it compatible with libreoffice or do I have to learn an altogether new language like python? Learning python would not be a problem but is not a solution to my problem as I have many work related files in excel which have a lot of vba code and it is not possible for me to use open office/libreoffice at work...
I just want to add that the function SinceLastWash gives the correct value in some cells where I use it and in others gives an error, #NAME?
Thanks

From LibreOffice's online help file:
With a few exceptions, Microsoft Office and LibreOffice cannot run the same macro code. Microsoft Office uses VBA (Visual Basic for Applications) code, and LibreOffice uses Basic code based on the LibreOffice API (Application Program Interface) environment. Although the programming language is the same, the objects and methods are different.
The most recent versions of LibreOffice can run some Excel Visual Basic scripts if you enable this feature at LibreOffice - PreferencesTools - Options - Load/Save - VBA Properties.
In reality, you would most likely need to sit down with the LibreOffice API and rewrite the functionality.

You must translate the portions that manipulate the document to use the UNO API. Sadly, this can be tricky depending on what your macro does. Basic statements work directly. Modifying a document generally does not.
Range(Cells(CurrentRow, i), Cells(CurrentRow, i)).Value = "a"
The Cells command returns a specific cell based on a row and column. So, you need the current row. Here is some craziness to get the active cell:
Sub RetrieveTheActiveCell()
Dim oOldSelection 'The original selection of cell ranges
Dim oRanges 'A blank range created by the document
Dim oActiveCell 'The current active cell
Dim oConv 'The cell address conversion service
Dim oDoc
oDoc = ThisComponent
REM store the current selection
oOldSelection = oDoc.CurrentSelection
REM Create an empty SheetCellRanges service and then select it.
REM This leaves ONLY the active cell selected.
oRanges = oDoc.createInstance("com.sun.star.sheet.SheetCellRanges")
oDoc.CurrentController.Select(oRanges)
REM Get the active cell!
oActiveCell = oDoc.CurrentSelection
oConv = oDoc.createInstance("com.sun.star.table.CellAddressConversion")
oConv.Address = oActiveCell.getCellAddress
Print oConv.UserInterfaceRepresentation
print oConv.PersistentRepresentation
REM Restore the old selection, but lose the previously active cell
oDoc.CurrentController.Select(oOldSelection)
End Sub
When you have the active cell, you get the cell address, and from that, you have the row. You do not need to use the range at all, since you only care about a single cell, so, you get the active sheet and then get a particular cell from the sheet.
Something like this:
ThisComponent.getCurrentController().getActiveSheet().getCellByPosition(nCol, nRow).getString() = "a"
I don't feel like figuring out what this does
Selection.SpecialCells(xlBlanks).Delete shift:=xlToLeft

In LibreOffice 4.4, the first subroutine will not work at all (I suspect due to all the variables beginning with 'xl'. The other two work perfectly if you change ThisCell to ActiveCell.
Rather than
Option VBASupport
I am using
Option VBASupport 1
Option Compatible

The only automatic tool I'm aware of is Business Spreadsheets (note that I have no personal or professional experience nor any affiliation with the site).
It seems specific to OpenOffice but I think it works with LibreOffice too.
In general though, you're better off doing this yourself, as the tool is far from perfect...

Selection.SpecialCells(xlBlanks).Delete shift:=xlToLeft deletes blank cells if I'm not mistaken

Related

Vlookup based on criteria

The data I have in my "entity sheet"
entity id
source id
source entity id
HR0001
GOP
1200
HR0002
WSS
WSS1201
HR0003
GOP
1201
HR0004
WSS-T
WSST1202
HR0005
GOP
1202
HR0006
GOP
1203
HR0007
WSS-S
WSSS1203
HR0008
GOP
1204
HR0009
GOP
1205
HR0010
GOP
1206
HR0011
WSS-R
WSSR1204
HR0012
WSS-T
WSST1205
HR0013
WSS-S
WSSS1206
HR0014
GOP
1207
HR0015
WSS-T
WSSS1207
HR0006
WSS-S
WSSS1208
HR0007
GOP
1208
HR0008
WSS-R
WSST1209
HR0009
WSS-S
WSSS1210
In my working sheet, I need the source entity id (column c) data, by doing a VLOOKUP on the entity id (column A), based on source id (column b). that is I need only those beginning with "WS" IDs on my working sheet. My code is
Sub Test()
Worksheets("working sheet").Activate
Dim sht, sht1 As Worksheet
Dim i As Long, LR As Long
Set sht = ActiveWorkbook.Worksheets("working sheet")
Set sht1 = ActiveWorkbook.Worksheets("entity sheet")
LR = sht.UsedRange.Rows.Count
With sht
For i = 2 To LR
If InStr(sht1.Range("B" & i).Value, "WS") Then
sht.Range("B" & i).Value = (Application.VLookup(.Range("A" & i).Value, Worksheets("entity sheet").Range("A2:C5000"), 3, False))
End If
Next i
End With
End Sub
desired result - in the working sheet
entity id
source entity id - WSS
HR0001
HR0002
WSS1201
HR0003
HR0004
WSST1202
HR0005
HR0006
WSSS1208
HR0007
WSSS1203
HR0008
WSST1209
HR0009
WSSS1210
HR0010
HR0011
WSSR1204
HR0012
WSST1205
HR0013
WSSS1206
HR0014
HR0015
WSSS1207
Took me a little while but... I've got two different versions for you: one with VBA and one with just formulas.
With VBA
The issue you had was that VLOOKUP returns the first match but you needed to satisfy two criteria (that is: (i) match on entity id and (ii) match on source id begins with "WS").
This meant that you either had to:
use a formula that could match both criteria at the same time, OR
find all matches with the first criteria (e.g. with FIND) and then loop through the results to match the second criteria -- probably something like this: https://www.thespreadsheetguru.com/the-code-vault/2014/4/21/find-all-instances-with-vba
I selected option #1 as I expected it would make the code shorter.
To do this, I took advantage of a trick I've used in formulas before where I can use "&" between two ranges to match on two criteria at the same time. So, instead of matching "HR0012" first and then "WS-something" second, I match "HR0012WS-something" at once. (You can view this concept by pasting =A2:A20&B2:B20 in an empty column somewhere in your entity sheet.)
The following code assumes that your active worksheet is your working sheet. Paste this code behind your working sheet, then run it when you have that sheet open.
Public Sub tester()
Dim rg As Range
Dim sSourceEntityId As String
For Each rg In Range("A2:A16")
sSourceEntityId = Evaluate("=XLOOKUP(""" & rg.Value & "WS"",entity!A2:A20&LEFT(entity!B2:B20,2),entity!C2:C20,"""",0,1)")
If Len(sSourceEntityId) > 0 Then
rg.Offset(0, 1).Value = sSourceEntityId
End If
Next rg
End Sub
If the part inside the Evaluate is not clear, paste
=XLOOKUP(A1&"WS",entity!A2:A20&LEFT(entity!B2:B20,2),entity!C2:C20,"",0,1)
somewhere inside your working sheet to see it more clearly.
Also, note that you used Instr, which would find "WS" anywhere in the string. I used LEFT(value, 2)="WS" to be sure that I matched only the first 2 characters.
I also had to use XLOOKUP instead of VLOOKUP to allow me to use the LEFT(value, 2). If you're using an old version of Excel, you won't have XLOOKUP, unfortunately.
Without VBA
Paste this formula into A2 on your working sheet:
=IFERROR(INDEX(entity!$C$2:$C$20,AGGREGATE(15,3,((entity!$A$2:$A$20&LEFT(entity!$B$2:$B$20,2)=A2&"WS")/(entity!$A$2:$A$20&LEFT(entity!$B$2:$B$20,2)=A2&"WS"))*ROW(entity!$A$2:$A$20)-ROW(entity!$A$1),1)),"")
Then copy that formula down to every row that you want to do a match on. Just to be clearer, it will look like this:
This is a little complex. I based it on an approach in this article, which explains step-by-step how to use INDEX(.. AGGREGATE(..)) for multiple matches. Although it's pretty neat how it works, you may prefer the VBA approach as it is probably easier to maintain.
UPDATE:
I forgot to mention that there is a possibility that the IFERROR() in the formula may slow your spreadsheet down if you have many matches and rows. I also created a version of the formula that doesn't use IFERROR. It uses an IF to first check if there are any TRUE matches first before executing the INDEX.. AGGREGATE. You may not need it, but I've pasted it below just in case it's useful:
=IF(MAX(INT(entity!$A$2:$A$20&LEFT(entity!$B$2:$B$20,2)=A2&"WS"))=1,INDEX(entity!$C$2:$C$20,AGGREGATE(15,3,((entity!$A$2:$A$20&LEFT(entity!$B$2:$B$20,2)=A2&"WS")/(entity!$A$2:$A$20&LEFT(entity!$B$2:$B$20,2)=A2&"WS"))*ROW(entity!$A$2:$A$20)-ROW(entity!$A$1),1)),"")
UPDATE 2:
The statement used in the VBA Evaluate will also work directly as a formula and is much simpler to understand. I realized this when I realized that a single valid match is okay (i.e. we don't need multiple matches):
=XLOOKUP(A2&"WS",entity!$A$2:$A$20&LEFT(entity!$B$2:$B$20,2),entity!$C$2:$C$20,"",0,1)

Textbox_change() event only on keyboard input

Function FtoC(ByVal Temp As Variant) As Variant
FtoC = Round((Temp - 32) * 5 / 9, 1)
End Function
Function CtoF(ByVal Temp As Variant) As Variant
CtoF = Round(Temp * 9 / 5 + 32, 1)
End Function
Private Sub Temp_F_Change()
If Temp_F <> "" Then Temp_C = FtoC(Temp_F)
End Sub
Private Sub Temp_C_Change()
If Temp_C <> "" Then Temp_F = CtoF(Temp_C)
End Sub
I have the above functions/subs for a little project to learn/sharpen my VBA skills. The logic is all fine and dandy. However, ideally I would only like the textbox_change event to only run if the change was due to a keyboard input (or a not sub output). A working example would be starting from blanks for each if I input 78 to Temp_F then it converts to 25.6 outputs it to Temp_C. Temp_C has now changed and it then converts 25.6 to 78.1 and outputs it to Temp_F, then of course temp_f has changed and it converts it to 25.6 which is not a change bringing both subs to their end points. Ideally i would like for it to take 78F convert to 25.6C and for the form to recognize that the Temp_C field change was due to a sub output(or not due to a keyboard input) and exit the temp_c_change() sub Is this even possible with VBA, if so how simple/complex is it? I tooled around with the idea of using a counter that is reset by a keypress event but my scripting skills aren't good enough to make it work. I thank you guys in advance and apologize that my question is beneath the talent that I've witnessed searching this site for answers before.
Well, for something this small you could use a worksheet_change event that first checks the target cell to see if either of the two temperature cells changed (assuming they do not move around the sheet - if they do, consider a named range). If either of those cells changed, then create the appropriate conversion formula as a string and put it in the other cell.
My apologies, I just noticed that you are working with a textbox event and not a worksheet event.
What happens to the data after it is converted? Does it go into a cell or get plugged into another function/sub? Right now, your code will just perform a calculation but doesn't show it or do anything else with it. Maybe you need to approach it from whatever that happens next.

Excel allows adding rows manually, but not from VBA

This has been asked already, but none of the answers available helps me. I am trying to add a row to a small worksheet. I am allowed to add the row with Alt-I, R manually, but if I try to do it from a macro, I get this:
I have tried, without effect, the following suggestions I have found on the Internet:
Check that data isn’t ridiculously long. Ctrl-End takes me to G40. The last available row is 1048576.
Unfreeze panes.
Execute “ActiveSheet.UsedRange” in the Immediate window.
Unmerge cells in row above the one I was inserting.
Rows("1048500:1048576").Delete. This ought to free up 76 rows, yet immediately after it attempting to insert just one row is forbidden.
Application.CutCopyMode = False
Selecting all the rows below those used and choosing “Clear Content”, save, close and reopen.
I am using Excel 2016. The only solution that looks at all plausible is using Application.SendKeys to do Alt-I, R, but I would rather not do that if I can help it. Neither the sheet nor the workbook containing it is protected. If you want to know what the offending code is:
For iWorksheetCounter = 2 To wbkFinal.Worksheets.Count
Set wksPartial = wbkFinal.Worksheets(iWorksheetCounter)
lngCurrentRow = iWorksheetCounter + iRowOffset ' iRowOffset = 3
wksTotals.Rows.Insert (lngCurrentRow + 1) ' this is not allowed for a reason I don't understand
wksTotals.Cells(lngCurrentRow, 1).Value = wksPartial.Name
Next ' iWorksheetCounter

Error while copying more than 8202 characters from one cell to another

Problem - I have around more than 8202 characters in once cell say Range("A1").
Now I would like to copy the content of cell(A1) to cell(A2) using VBA. I'm using below Code
Sheets("XYZ").Range("A2") = Sheets("XYZ").Range("A1")
After the execution of the Code. It gives "Application Defined Or Object Defined Error !!"
Please help/assist with your expert comments.
Observation - If I reduce the length of "A1" cell to 8202 or less then about code works!
I'm Confused. Pls assist.
Change your code to
Sheets("XYZ").Range("A2") = Sheets("XYZ").Range("A1").Value
and it will work.
Not really sure why though, as .Value is the default property of a range.
I was able to duplicate your error with the following:
Sub Test8202Copy()
Dim wks As Worksheet
Set wks = Worksheets("Sheet1")
Dim x As String
For i = 0 To 8202
x = x + "a"
Next i
wks.Range("A1").Value = x
wks.Range("A2") = wks.Range("A1")
End Sub
I was able to solve the error by adding .value to the copy.
Sub Test8202Copy()
Dim wks As Worksheet
Set wks = Worksheets("Sheet1")
Dim x As String
For i = 0 To 8202
x = x + "a"
Next i
wks.Range("A1").Value = x
wks.Range("A2").Value = wks.Range("A1").Value
End Sub
Using an intermediate variable without the use of .Value seems to work:
Dim y As Variant
y = wks.Range("A1")
wks.Range("A2") = y
My guess so far is that 8202 exceed the character limit of the data type used when you don't define .Value. The in cell limit length is 32,767 (MS Excel 2010) which is almost 4x the 8201 value that clears.
#Chris Neilsen provided the most practical and elegant solution to the problem (his code snippet follows):
Sheets("XYZ").Range("A2") = Sheets("XYZ").Range("A1").Value
In order to investigate and understand the possible cause of this strange behavior (may be a bug) of the Range object, I've posted couple comments, which are summarized below:
There is a conceptual difference between the original expression (see below):
Sheets("XYZ").Range("A2") = Sheets("XYZ").Range("A1")
and solution proposed by #Chris Neilsen, namely: original expression is implicitly assigning the Range object var (essentially, a pointer) to another Range object, like demonstrated in the following code snippet with explicit assignment:
Set rng = Sheets("XYZ").Range("A1")
Sheets("XYZ").Range("A2") = rng
while proposed solution explicitly passes the value property. Still, the reason why assigning a Range object failed for a value with string.Length>8202 is currently unclear (it may be caused by some internal nuances of the Excel Range object implementation).
Many thanks for posting this interesting question and fruitful discussion.
Regards,
This limit (see below re excel-2007) is covered in this MSDN article although interestingly it implies a vba array is invoved
Separately as per https://stackoverflow.com/a/13665363/641067 excel-2003 cant handle array strings longer than 911 characters, whereas the article below references 1823 character
SYMPTOMS
When you run a Microsoft Visual Basic for Applications (VBA) macro to transfer data from a VBA array that contains strings of data to a range of cells in a Microsoft Excel worksheet, the data may be truncated (cut off).
Note In Microsoft Office Excel 2003 and in later versions of Excel, you may receive the following error message when you run the VBA macro in the Visual Basic Editor:
Run-time error '1004'
CAUSE
This problem may occur when one of the following conditions is true:
In Excel 2007, the VBA array is longer than 8,203 characters in length.
In Excel 2003 and in earlier versions of Excel, the VBA array is longer than 1,823 characters in length

Excel ran out of resources while attempting to calculate one or more formulas

I have a workbook to do 'smart'-graphs on my expenses. It's been running for a year and there are now a lot of graphs and expenses. Excel now throws an out-of-resources error whenever I change anything or open the workbook. Thing is, I have lots of resources and its not using hardly any of them.
Win8 64bit w/ 8 core CPU and 32GB of ram
Office 2013 64bit
I have 2 sheets, the first sheet called Expenses has 3 columns [Date,Description,Amount] and about 1500 rows of data. The second sheet has a LOT (500 or so) of formulas that are all the same and aim to do "Sum all expenses between date X and Y where description matches -some needle-". The formula I have is this:
=
ABS(
SUMPRODUCT(
--(Expenses!A:A >= DATE(2011,12,1)),
--(Expenses!A:A < DATE(2012,1,1)),
--(ISNUMBER(FIND(C50,Expenses!B:B))),
Expenses!C:C
)
)
Can I give Excel more resources? (I'm happy for it to use all my ram, and chug my CPU for a few minutes).
Is there a more efficient way I can do this formula?
I understand that this formula is creating a large grid and masking my expenses list with it, and that for each formula this grid has to get created. Should I create a macro to do this more efficiently instead? If I had a macro, I would want to call it from a cell somehow like
=sumExpenses(<startDate>, <endDate>, <needle>)
Is that possible?
Thanks.
I had a similar problem where there were a few array formulas down about 150 rows and I got this error, which really baffled me because there really aren't that many formulas to calculate. I contacted our IT guy and he explained the following, some of which I understand, most of which I don't:
Generally when the computer tries to process large amounts of data, it uses multi-threaded calculation, where it uses all 8 processors that the computer tricks itself into thinking it has. When multi-threaded calculation is turned off, the computer doesn't throw the 'Excel ran out of resources...' error.
To turn off multi-threaded calculation, got to the 'File' tab in your Excel workbook and select 'Options'. On the right side of the box that appears select 'Advanced' and scroll down to the heading 'Formulas'. Under that heading is a check box that says 'Enable multi-threaded calculation'. Untick it, then select 'OK' and recalculate your formulas.
I had a go at creating a function that hopefully replicates what your current equation does in VBA with a few differences. Since I don't know the specifics of your second sheet the caching might not help at all.
If your second sheet uses the same date range for all calls to sumExpenses then it should be a bit quicker as it pre-sums everything on the first pass, If your date range changes throughout then its just doing a lot of work for nothing.
Public Cache As Object
Public CacheKey As String
Public Function sumExpenses(ByVal dS As Date, ByVal dE As Date, ByVal sN As String) As Variant
Dim Key As String
Key = Day(dS) & "-" & Month(dS) & "-" & Year(dS) & "_" & Day(dE) & "-" & Month(dE) & "-" & Year(dE)
If CacheKey = Key Then
If Not Cache Is Nothing Then
If Cache.Exists(sN) Then
sumExpenses = Cache(sN)
Exit Function
End If
Set Cache = Nothing
End If
End If
CacheKey = Key
Set Cache = CreateObject("Scripting.Dictionary")
Dim Expenses As Worksheet
Dim Row As Integer
Dim Item As String
Set Expenses = ThisWorkbook.Worksheets("Expenses")
Row = 1
While (Not Expenses.Cells(Row, 1) = "")
If Expenses.Cells(Row, 1).Value > dS And Expenses.Cells(Row, 1).Value < dE Then
Item = Expenses.Cells(Row, 2).Value
If Cache.Exists(Item) Then
Cache(Item) = Cache(Item) + Expenses.Cells(Row, 3).Value
Else
Cache.Add Item, Expenses.Cells(Row, 3).Value
End If
End If
Row = Row + 1
Wend
If Cache.Exists(sN) Then
sumExpenses = Cache(sN)
Else
sumExpenses = CVErr(xlErrNA)
End If
End Function
Public Sub resetCache()
Set Cache = Nothing
CacheKey = ""
End Sub
There could be many causes of this. I just wish Excel would tell us which one (or more) of the 'usual suspects' is committing the offence of RAM hogging at this time.
Also look for
Circular references
Fragmented Conditional formatting (caused by cutting, pasting, sorting, deleting and adding cells or rows.
Errors resulting in #N/A, #REF, #DIV/0! etc,
Over-use of the volatile functions TODAY(), NOW(), etc.
Too many different formats used
... in that order
While you're there, check for
Broken links. A formula relying on a fresh value from external data could return an error.
Any formulas containing #REF!. If your formulas are that messed these may well be present also. They will not cause an error flag but may cause some unreported errors. If your formulas are satisfied by an earlier condition the part of the formula containing #REF! will not be evaluated until other conditions prevail.
Fragmented conditional formatting was the case for me.
Older versions of the same workbook did not have an issue. Today, I cut/pasted many cells and the issue started occurring.
Removing the columns where I was cutting/pasting resolved the issue for me.
This is difficult to diagnose since conditional formatting does not immediately standout like normal formulas.

Resources