Making a formula a VBA Macro - vba

I got the following code
=LEFT(A2, MIN(ROW(INDIRECT("1:"&LEN(A2)))+(((CODE(MID(UPPER(A2),
ROW(INDIRECT("1:"&LEN(A2))), 1))>64)*(CODE(MID(UPPER(A2),
ROW(INDIRECT("1:"&LEN(A2))), 1))<91))+
((CODE(MID(A2, ROW(INDIRECT("1:"&LEN(A2))), 1))>47)*
(CODE(MID(A2, ROW(INDIRECT("1:"&LEN(A2))), 1))<58)))*1E+99)-1)
I have this code and a few others, but how can I make it into a macro applicable to my entire workbook? I know its probably the same as a macro in terms of time, but I eventually want to loop it throughout a directory and would help automate a process. Is there a way to make this a macro for my workbook?

The crudest quickest way would be something like this:
Range("J2:J5000").Formula = "=LEFT(A2, MIN(ROW(INDIRECT(""1:""&LEN(A2)))+(((CODE(MID(UPPER(A2), ROW(INDIRECT(""1:""&LEN(A2))), 1))>64)*(CODE(MID(UPPER(A2), ROW(INDIRECT(""1:""&LEN(A2))), 1))<91))+((CODE(MID(A2, ROW(INDIRECT(""1:""&LEN(A2))), 1))>47)*(CODE(MID(A2, ROW(INDIRECT(""1:""&LEN(A2))), 1))<58)))*1E+99)-1)"
Which will put your exact formula in the range (and update itself according to the row reference). Obviously the reference to column J can be changed and the 5000 can be made dynamic using rows.count).end(xlup).row but without knowing which columns to play with I just had to take a stab at a crude solution.
However depending on what your "symbols" could be a solution using the split command would most likely be the better choice. Can you post more direction on this? Then I can edit this answer and add a code solution in for that for you.
Also include some sample data and expected results, maybe 10 rows worth to give a good set for testing
For an example of how the split command works select one of the cells with data in it that you need to split on the underscore and go to the debug window in the VBE (CTRL-G) and enter this (including the question mark) then press enter.
?split(Activecell.text,"_")(0)
Now update the 0 to 1 and press enter. This will show you how this command works, it splits a string to an array based on the delimiter you give it.
EDIT:
This code will do what you want, Notice how Split is being used.
Function GetFirstPart(SplitString As String)
Dim PosibleSplits As Variant, X As Long
PossibleSplits = Array("_", "+", "-")
For X = LBound(PossibleSplits) To UBound(PossibleSplits)
If Len(SplitString) <> Len(Split(SplitString, PossibleSplits(X))(0)) Then
GetFirstPart = Split(SplitString, PossibleSplits(X))(0)
Exit For
End If
Next
End Function
Use it by pasting the code into a module then in your sheet use it the same as any other formula =GetFirstPart(A1) where A1 has the string to split, drag down as far as your data goes.
You can add other delimiters in this line PossibleSplits = Array("_", "+", "-")

Related

How to stop 'Text' changing to 'text' automatically [duplicate]

For unknow reason my Excel VBA editor changes:
Cells(ActiveCell.Row, 1).Value = MyString
into
Cells(ActiveCell.row, 1).Value = MyString
Word "Row" should start with capital "R" but after I type it, it changes to small "r". I have checked the code and I am sure I do not use "raw" as a variable. The macro itself works fine as if it was written "Row". On other workbooks everything is ok (R is capitalized).
Anybody has idea why it happens?
I was also getting a bit tired of from looking where exactly I have declared a Row with a small letter, as far as it was not declared anywhere.
Thus, found a great solution - add the following in a module:
Public Sub TestMe
Dim Row as Long
End Sub
And see the whole code changing. Then you may delete it. Or simply write Dim Row as Long on a new line, somewhere in your code. And then delete it.
VBA isn't case sensitive, so I wouldn't lose too much sleep over it. The editor tries to convert all of the variable cases to however it was dimmed. Most likely the ActiveCell definition was screwed up somehow.
I used variable row in VBA of that worksheet. Then I changed the name of the variable row to something else like MyRowName Although there was no such variable as row in VBA anymore, it still kept lower case for that word. As I mentioned above everything worked fine i.e. ActiveCell.row returned what it should for ActiveCell.Row.
For just aesthetic reasons, I have copied the whole VBA to another worksheet and the bug was crunched. Row returned to Upper case.

VBA Word Why doesn't .BaseStyle stay capitalized? [duplicate]

For unknow reason my Excel VBA editor changes:
Cells(ActiveCell.Row, 1).Value = MyString
into
Cells(ActiveCell.row, 1).Value = MyString
Word "Row" should start with capital "R" but after I type it, it changes to small "r". I have checked the code and I am sure I do not use "raw" as a variable. The macro itself works fine as if it was written "Row". On other workbooks everything is ok (R is capitalized).
Anybody has idea why it happens?
I was also getting a bit tired of from looking where exactly I have declared a Row with a small letter, as far as it was not declared anywhere.
Thus, found a great solution - add the following in a module:
Public Sub TestMe
Dim Row as Long
End Sub
And see the whole code changing. Then you may delete it. Or simply write Dim Row as Long on a new line, somewhere in your code. And then delete it.
VBA isn't case sensitive, so I wouldn't lose too much sleep over it. The editor tries to convert all of the variable cases to however it was dimmed. Most likely the ActiveCell definition was screwed up somehow.
I used variable row in VBA of that worksheet. Then I changed the name of the variable row to something else like MyRowName Although there was no such variable as row in VBA anymore, it still kept lower case for that word. As I mentioned above everything worked fine i.e. ActiveCell.row returned what it should for ActiveCell.Row.
For just aesthetic reasons, I have copied the whole VBA to another worksheet and the bug was crunched. Row returned to Upper case.

How to apply a Excel formula in VBA macro to find the 2nd to last word

I am quite new to VBA but have been working with Excel a bit. I created a formula that does exactly what I need it to do: find the Nth to last word (mostly 2nd or 3rd to last) in a cell. I think my main issue is how to apply a formula to a range of cells without overwriting the cell and how to use excel formulas in VBA. The Excel formula I use is as follows
=TRIM(LEFT(RIGHT(" "&SUBSTITUTE(TRIM(A1)," ",REPT(" ",60)),180),60))
It might not be the most eloquent way but it works pretty well in Excel. Changing the number 180 to 60 will give last word, 120 2nd to last and so on. But in VBA it gives 1st syntax error and when I get it to run without the arguments and with only TRIM(A1) it overwrites the cell. The code I use is as follows (referencing only A1 to test it):
reportsheet.Range("A1").Formula = "=TRIM(LEFT(RIGHT(" " & SUBSTITUTE(TRIM(A1)," " ,REPT(" ",60)),180),60))"
My macro searches and extracts specific data from Sheet1 to Sheet2. Now I would want to apply this (or a similar) formula to the data it extracts to the Sheet2. I have tried a lot of different things from using VBA's own trim to making a completely custom function. None of it seems to work and I think it is down to a misunderstanding on how Excel formulas and VBA play together.
In addition I am trying to find a way to find the only numbers in the cell and trim out everything else. Any help with this would also be appreciated.
EDIT: Sorry guys, I had a mistake in the code I provided, it should have been referring to A1 in both instances.
Double up quotes within a quoted string or use alternatives.
reportsheet.Range("A1").Formula = "=TRIM(LEFT(RIGHT("" "" & SUBSTITUTE(TRIM(A20),"" "" ,REPT("" "",60)),180),60))"
'alternative
reportsheet.Range("A1").Formula = "=TRIM(LEFT(RIGHT(char(32) & SUBSTITUTE(TRIM(A20), char(32), REPT(char(32),60)),180),60))"
Doubling the quotes as #Jeeped and the commenters wrote is solving your issue with the formula.
As an alternative, you could write a function ("UDF") that returns the n-th word of a string. It is rather easy by using the VBA function split that returns an array of strings. Put the following code in a Module:
Public Function getWord(s As String, ByVal n As Integer) As String
n = n - 1 ' Because Array index will start at 0
Dim arr() As String
arr = Split(s, " ")
If UBound(arr) >= n Then
getWord = arr(n)
End If
End Function
In Excel, you write for example =getWord(A20, 3) as formula

How to code Excel VBA equivalent of INDIRECT function?

I have many uses of the INDIRECT function in my workbook, and it is causing performance issues. I need to replace them with something that will give me the same results. All the INDIRECTS recalculate anytime anything is changed, causing the workbook to lag.
I was wondering if there is a way to code INDIRECT in VBA without actually using the INDIRECT function, and take away the volatility of the function in the code.
=INDIRECT("'" & $AC$9 & "'!" & AC26)
This is an example. I need to remove INDIRECT but get the same results for this cell. Is there a way to accomplish this in VBA?
You can try this.
Place the following routines in a standard code module:
Public Function INDIRECTVBA(ref_text As String)
INDIRECTVBA = Range(ref_text)
End Function
Public Sub FullCalc()
Application.CalculateFull
End Sub
Replace the INDIRECT functions in your formulas with INDIRECTVBA.
These will be static. If the slowness of your workbook is because your INDIRECTs are constantly evaluating, then this will put an end to that.
IMPORTANT: all cells that contain a formula using INDIRECTVBA will be static. Each formula will calculate when you confirm it, but it will not recalculate when precedents change.
You will then need a way to force them to recalculate at a convenient time. You can do that from the Ribbon. Or, you can run FullCalc.
Was going to add this as a comment, but my thought process got too long.
What is the context of the problem you are trying to solve?
I am guessing you are using some kind of data validation drop-down menu in $AC$9 to select a sheet name and then all your INDIRECT formulas are providing a mirror image of a particular section of the user-specified worksheet.
If that is the case then you might consider using INDEX as an alternative. It is written as =INDEX(Range, RowNum, ColNum) E.g. if you put this in H20: =INDEX(Sheet1!A:Z,ROW()+10,COLUMN()-5) then it would reflect whatever is in sheet 1, cell C30 (H - 5 columns, 20 + 10 rows). Of course, you don't have to offset anything if you don't want to, I just wanted to demonstrate that as an option.
Now, the trickier part would still remain - assigning/updating the SheetName variable. This could be done with a UserForm instead of typing in a value in a particular input cell. For example, you could have VBA provide an input box/dropdown menu for the user to select one of the available sheet names, then take that input and use it in a quick find and replace instruction - searching for "=INDEX(*!" and replacing with "=INDEX(" & InputVariable & "!"
I've made a few assumptions about your dataset and what you're trying to achieve, so it might not be the ideal solution, but perhaps something to think about.
The solution to volatility with the Indirect function (typical in multi-version cross platform use and partitioning to run Windows on Mac) can be absorbed by splitting its various functions with a pseudonym for Indirect I have named "Implied":
Public Function Implied(Varient)
' CREDIT: Stephen L. Rush
On Error Resume Next
If IsError(Range(Varient)) Then
If IsError(Find(Varient, "&")) Then
'Not Range, is Indirect. "A" & Match() Style (where Match() = row).
Implied = WorksheetFunction.Indirect(Varient)
Else
'Not a Range, not Indirect. "A" & B99 Reference (where B99 = row).
Implied = Range(Left(Varient, Find(Varient, "&") - 1) & Range(Right(Varient, Len(Varient) - Find(Varient, "&"))))
End If
Else
'Is pure Range
Implied = Range(Varient)
End If
'[On Error GoTo 0] Conflicts with use as formula
End Function

Iterating 100 cells takes too long

In my excel VBA code, I need to move some data from a range to another sheet.
As of now, I'm iterating through the range and copying the values like this:
For offset = 0 To 101
ActiveWorkbook.Sheets(Sheet).Range("C3").offset(offset, 0).Value = ActiveSheet.Range("D4").offset(offset, 0).Value
Next offset
However, it takes almost a minute to iterate and copy the values for the 100 cells.
Would I be better off using Copy-Paste programatically, or is there a way to copy for the entire range at once? Something like:
ActiveWorkbook.Sheets(Sheet).Range("C3:C102").Value = ActiveSheet.Range("D4:D104").Value
You can read the entire range at once into a Variant array, and then write it back to another range. This is also quick, flickerless, and has the added bonus that you can code some operations on the data if you are so inclined.
Dim varDummy As Variant
varDummy = ActiveSheet.Range("D4:D104")
' Can insert code to do stuff with varDummy here
Workbook.Sheets(Sheet).Range("C3:C103") = varDummy
This I learned the hard way: Avoid Copy/Paste if at all possible! Copy and Paste use the clipboard. Other programs may read from / write to the clipboard while your code is running, which will cause wild, unpredictable results.
Also, it's generally a good idea to minimize the number of interactions between VBA and Excel, because they are slow. Having such interactions in a loop is multiply slow.
So, silly me did not try before posting here. Apparently, I can move data for an entire range this way:
Workbook.Sheets(Sheet).Range("C3:C102").Value = ActiveSheet.Range("D4:D104").Value
Its as fast as copy-paste without the switching of sheets.
Iterating through the range using a for loop takes about 45s for 100 cells, while the above two options are instant.
You can speed up code and stop flickering with:
Application.ScreenUpdating = False
'YOUR CODE
Application.ScreenUpdating = True
More: http://www.ozgrid.com/VBA/excel-macro-screen-flicker.htm
Columns("A:Z").Select
Selection.Copy
Sheets("Sheet2").Select
Range("A1").Select
ActiveSheet.Paste
That will copy columns A to Z from Sheet 1 to Sheet 2. This was generated by recording the macro. You can also apply it to ranges with something like this:
Range("D4:G14").Select
Selection.Copy
Sheets("Sheet2").Select
Range("D4").Select
ActiveSheet.Paste
Is this something like what you're after?
If you need anything specific and you can do it manually (e.g. copy and paste), record the macro to get the VBA code for it.
Copy and pasting has a decent amount of overhead in VBA, as does dealing with ranges like that. Its been a while since I have done VBA but if I recall correctly the fastest way to do something like this is to write the values you want into an array and then use the Resize function. So something like this:
Option Base 0
Dim firstrow as integer
Dim lastrow as integer
Dim valuesArray() as Long
Dim i as integer
//Set firstrow and lastrow however you deem appropriate
...
//Subtracing first row from last row gets you the needed size of the 0 based array
ReDim valuesArray(lastrow-firstrow)
for int i = 0 to (lastrow-firstrow)
valuesArray(i)=Cells(i+firstrow, COLUMNNUMBER).value
next i
Of course replace COLUMNNUMBER with whatever column it is you are iterating over. This should fill your array with your desired values. Then pick your destination cell and use Resize to put the values in. So if your destination cell is D4:
Range("D4").Resize(UBound(valuesArray)+1, 0).value = valuesArray
That write all the values in the array starting at D4 and going down to as many cells are in the array. Slightly more complicated but if you are going for speed I don't think I have ever come up with anything faster. Also I did this off the top of my head so please test and make sure that you don't cut off a cell here and there.
That OZGrid page has very useful info - http://www.ozgrid.com/VBA/SpeedingUpVBACode.htm
In my case, I need the formatting to be copied as well so I have been using this:
Sheet1.Range("A1:A200").Copy Destination:=Sheet2.Range("B1")
but was still having very slow execution - to the point of locking up the application - I finally found the problem - at some point in the past a number of empty text boxes got into my page - and while they were copied each time my code ran they were not erased by my code to clear the working area. The result was something like 4,500 empty text boxes - each of which was copy and pasted by even the code above.
If you use Edit - Go To... - Click on Special - then choose Objects - and you don't see anything that is good - if you see a bunch of objects that you were not aware of on your page that is not good.