Call function in Loop VBA - vba

My problem is simple for VBA pro. if you can help me to understand please.
I am trying to call a function which keep only caps in a cell and past the result in the next column by looping all the rows. Please take a look at the code below.
Thank you.
Option Explicit
Sub LLOP()
Dim i As Integer
i = 1
Do While Cells(i, 10).Value <> ""
Cells(i, 11).Value = Cells(i, 10).Value = ExtractCap
i = i + 1
Loop
End Sub
Option Explicit
Function ExtractCap(Txt As String) As String
Application.Volatile
Dim xRegEx As Object
Set xRegEx = CreateObject("VBSCRIPT.REGEXP")
xRegEx.Pattern = "[^A-Z]"
xRegEx.Global = True
ExtractCap = xRegEx.Replace(Txt, "")
Set xRegEx = Nothing
End Function

Try something like as follows. Notes to follow.
1) Extract cap requires an argument which is the string you want to replace. I have used the value in the adjacent column
2) Option Explicit should only occur once at the top of the module
3) As you are looping rows uses Long not Integer to avoid potential overflow
4) Comparison with vbNullString is faster than empty string literal ""
Edit:
5) See #Jeeped's comment re Static xRegEx As Object followed by if xregex is nothing then Set xRegEx = CreateObject("VBSCRIPT.REGEXP") which significantly improves performance when called in a loop as the regex object only gets created once
Option Explicit
Sub LLOP()
Dim i As Long
i = 1
With ThisWorkbook.Worksheets("Sheet1") 'change as appropriate
Do While .Cells(i, 10).Value <> vbNullString 'column J
.Cells(i, 11).Value = ExtractCap(.Cells(i, 10).Text) 'column K
i = i + 1
Loop
End With
End Sub
Public Function ExtractCap(Txt As String) As String
Application.Volatile
Dim xRegEx As Object
Set xRegEx = CreateObject("VBSCRIPT.REGEXP")
xRegEx.Pattern = "[^A-Z]"
xRegEx.Global = True
ExtractCap = xRegEx.Replace(Txt, vbNullString)
End Function

Assuming that you want to enter a custom =ExtractCap() formula in the 11. column, with a parameter of the 10. column, this is a possible solution:
Option Explicit
Sub LLOP()
Dim i As Long: i = 1
Do While Cells(i, 10).Value <> ""
Cells(i, 11).Formula = "=ExtractCap(""" & Cells(i, 10) & """)"
i = i + 1
Loop
End Sub
Function ExtractCap(Txt As String) As String
Application.Volatile
Static xRegEx As Object
If xRegEx Is Nothing Then Set xRegEx = CreateObject("VBSCRIPT.REGEXP")
xRegEx.Pattern = "[^A-Z]"
xRegEx.Global = True
ExtractCap = xRegEx.Replace(Txt, "")
End Function
The .Formula passes the function ExtractCap as a formula with its parameter of Cells(i, 10).

Try below alternative code. Your method is complicated and uses regular expressions (which is nice, but in your case, ineffective).
The code:
Option Explicit
Sub LLOP()
Dim i As Integer
i = 1
'indentation! in your original code, you didn't have proper indentation
'I know that VBA editor don't indent code automatically, but it's worth the effort
Do While Cells(i, 10).Value <> ""
' invalid syntax!
' first, this is kind of multiple assignment (I don't know what are you trying to do)
' secondly, you call your function without arguments
' Cells(i, 11).Value = Cells(i, 10).Value = ExtractCap
' I guess you wanted something like this
Cells(i, 11).Value = ExtractCap(Cells(i, 10).Value)
'or using my function:
Cells(i, 11).Value = SimpleExtractCap(Cells(i, 10).Value)
i = i + 1
Loop
End Sub
'THIS IS YOUR FUNCTION, which is complicated (unnecessarily)
Function ExtractCap(Txt As String) As String
Application.Volatile
Dim xRegEx As Object
Set xRegEx = CreateObject("VBSCRIPT.REGEXP")
xRegEx.Pattern = "[^A-Z]"
xRegEx.Global = True
ExtractCap = xRegEx.Replace(Txt, "")
Set xRegEx = Nothing
End Function
'this is my alternative to your function, which is very simple and basic
Function SimpleExtractCap(Txt As String) As String
SimpleExtractCap = ""
Dim i As Long, char As String
For i = 1 To Len(Txt)
char = Mid(Txt, i, 1)
'if we have upper-case letter, then append it to the result
If isLetter(char) And char = UCase(char) Then
SimpleExtractCap = SimpleExtractCap & char
End If
Next
End Function
Edit:
In order to check if given character is letter, you'll need additional function:
Function isLetter(letter As String) As Boolean
Dim upper As String
upper = UCase(letter)
isletter = Asc(upper) > 64 And Asc(upper) < 91
End Function
Now, I added this function to code, to check if character is letter.

Related

Insert space between text and number for cells within column

I've already written a code that inserts a space between text and numbers, separating 'unspaced' days and months from dates, and it works as it's supposed to.
The only problem is that I'm using an If then structure to determine which Regular Expressions pattern I should use.
If the first character of the date is a number, then knowing that it is in the 'DayMonth' sequence, I use this pattern: "(.*\d)(?! )(\D.*)". Otherwise, assuming that it isn't in the 'DayMonth' sequence but rather in the 'MonthDay' sequence, I use the other pattern: "(.*\D)(?! )(\d.*)".
Is there any way to use two patterns at once for the Regular Expressions object to scan through so that I can get rid of the If Then structure?
My code below:
Sub SpaceMonthDayIf()
Dim col As Range
Dim i As Long
Set col = Application.InputBox("Select Date Column", "Obtain Object Range", Type:=8)
With CreateObject("VBScript.RegExp")
For i = 1 To Cells(Rows.Count, col.Column).End(xlUp).Row
If IsNumeric(Left(Cells(i, col.Column).Value, 1)) Then
.Pattern = "(.*\d)(?! )(\D.*)"
Cells(i, col.Column) = .Replace(Cells(i, col.Column), "$1 $2")
Else
.Pattern = "(.*\D)(?! )(\d.*)"
Cells(i, col.Column) = .Replace(Cells(i, col.Column), "$1 $2")
End If
Next
End With
End Sub
For clarity, here's what happens when I run my code:
Try this code
Sub Test()
Dim a, i As Long
With Range("A2", Range("A" & Rows.Count).End(xlUp))
a = .Value
With CreateObject("VBScript.RegExp")
.Global = True
.Pattern = "(\d+)"
For i = 1 To UBound(a, 1)
a(i, 1) = Application.Trim(.Replace(a(i, 1), " $1 "))
Next i
End With
.Columns(2).Value = a
End With
End Sub
You can avoid that by inserting your space differently. Here is a Function written with early-binding, but you can change that to late-binding.
Match the junction between a letter and a number, then construct a string, inserting a space appropriately.
Option Explicit
Function InsertSpace(S As String) As String
Const sPat As String = "[a-z]\d|\d[a-z]"
Dim RE As RegExp, MC As MatchCollection
Set RE = New RegExp
With RE
.Global = False
.Pattern = sPat
.IgnoreCase = True
If .Test(S) = True Then
Set MC = .Execute(S)
With MC(0)
InsertSpace = Left(S, .FirstIndex + 1) & " " & Mid(S, .FirstIndex + 2)
End With
End If
End With
End Function
You can also accomplish this without using Regular Expressions:
EDIT Pattern change for Like operator
Option Explicit
Option Compare Text
Function InsertSpace2(S As String) As String
Dim I As Long
For I = 1 To Len(S)
If Mid(S, I, 2) Like "#[a-z]" Or Mid(S, I, 2) Like "[a-z]#" Then
InsertSpace2 = Left(S, I) & " " & Mid(S, I + 1)
Exit Function
End If
Next I
End Function

Identify it then Move It (Macro)

I had this project in Chemistry to supply a list of Compound elements
now I had found a website where it gives me a very long list of elements:
I had made this Code but it Doesn't Work
Sub move()
Dim list As Range
Set list = Range("A1:A2651")
For Each Row In list.Rows
If (Row.Font.Regular) Then
Row.Cells(1).Offset(-2, 1) = Row.Cells(1)
End If
Next Row
End Sub
Can you make it run for me? you can have your own algorithm ofc.
Assuming the list is constantly in the same format (i.e. Compound name, empty line, Compound Symbols, empty line) this quick code will work:
Sub move()
Dim x As Integer
x = 3
With ActiveSheet
Do Until x > 2651
.Cells(x - 2, 2).Value = .Cells(x, 1).Value
.Cells(x, 1).ClearContents
x = x + 4
Loop
End With
End Sub
After running you can then just sort columns A:B to remove the blanks.
After trying your original code I realised the problem was with the .regular property value. I've not seen .regular before, so swapped it to NOT .bold instead, and to ignore blank entries, then added the line for clearing the contents of the cell copied. This is most like the original code for reference:
Sub get_a_move_on()
Dim list As Range
Set list = ActiveSheet.Range("A1:A2561")
For Each Row In list.Rows
If Row.Font.Bold = False And Row.Value <> "" Then
Row.Cells(1).Offset(-2, 1) = Row.Cells(1)
Row.Cells(1).ClearContents
End If
Next Row
End Sub
P.S it's a list of compounds, not elements, there's only about 120 elements in the periodic table! ;)
Another way to retrieve the data you need via XHR and RegEx:
Sub GetChemicalCompoundsNames()
Dim sRespText As String
Dim aResult() As String
Dim i As Long
' retrieve HTML content
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://quizlet.com/18087424", False
.Send
sRespText = .responseText
End With
' regular expression for rows
With CreateObject("VBScript.RegExp")
.Global = True
.MultiLine = True
.IgnoreCase = True
.Pattern = "qWord[^>]*?>([\s\S]*?)<[\s\S]*?qDef[^>]*?>([\s\S]*?)<"
With .Execute(sRespText)
ReDim aResult(1 To .Count, 1 To 2)
For i = 1 To .Count
With .Item(i - 1)
aResult(i, 1) = .SubMatches(0)
aResult(i, 2) = .SubMatches(1)
End With
Next
End With
End With
' output to the 1st sheet
With Sheets(1)
.Cells.Delete
Output .Range("A1"), aResult
End With
End Sub
Sub Output(oDstRng As Range, aCells As Variant)
With oDstRng
.Parent.Select
With .Resize( _
UBound(aCells, 1) - LBound(aCells, 1) + 1, _
UBound(aCells, 2) - LBound(aCells, 2) + 1 _
)
.NumberFormat = "#"
.Value = aCells
.Columns.AutoFit
End With
End With
End Sub
Gives output (663 rows total):

Automatically adapt listbox column width

I programmatically add elements from a database to a multicolumn listbox using this code :
Do While (Not rs.EOF)
ExistingSheetsListBox.AddItem
ExistingSheetsListBox.List(i, 0) = rs.Fields(0)
ExistingSheetsListBox.List(i, 1) = rs.Fields(1)
ExistingSheetsListBox.List(i, 2) = rs.Fields(2)
ExistingSheetsListBox.List(i, 3) = rs.Fields(3)
ExistingSheetsListBox.List(i, 4) = rs.Fields(4)
i = i + 1
rs.MoveNext
Loop
The insertion in the listbox works fine, but the column width is not always adapted to the length of the elements inserted in it, I would like to know how I can do so that the column width of each column is adapted to the text inserted into it.
EDIT : I used the solution proposed by #Excel Developers with the piece of code given by #HarveyFrench.
There is no autosize option, following sample code shows 2 ways to do this.
This does not take into account anything other than being a sample.
Class Module clsListCtrlWidths
'class option used so we can use Collection instead of an array.
Option Explicit
Public m_ColWidthMax As Long
Forms Module. Initialise somewhere
Dim l_ColumnWidths As Collection
Set l_ColumnWidths = New Collection
Forms Module functions
Private Function SetColWidth(stLen As String, ctCol1 As control, lPosCol As Long) As String
Dim stWidthTemp As String
If lPosCol > 0 Then
stWidthTemp = stLen & ";"
End If
Dim lTmpWidth As Long
Dim lColWidth As Long
lTmpWidth = ctCol1.Width
ctCol1.AutoSize = True
lColWidth = ctCol1.Width
ctCol1.AutoSize = False
ctCol1.Width = lTmpWidth
If l_ColumnWidths.Count > lPosCol Then
If l_ColumnWidths.Item(lPosCol + 1).m_ColWidthMax < lColWidth Then
l_ColumnWidths.Item(lPosCol + 1).m_ColWidthMax = lColWidth
Else
lColWidth = l_ColumnWidths.Item(lPosCol + 1).m_ColWidthMax
End If
Else
Dim clsColWidth As clsListCtrlWidths
Set clsColWidth = New clsListCtrlWidths
clsColWidth.m_ColWidthMax = lColWidth
l_ColumnWidths.Add clsColWidth
End If
stWidthTemp = stWidthTemp & lColWidth
SetColWidth = stWidthTemp
End Function
Following function takes listbox & calls on above function;
Private Function AutoSizeColsWidth(ByRef ctListCtrl As MSForms.ListBox)
Dim txtBoxDummy As control
Set txtBoxDummy = Me.Controls.Add("Forms.TextBox.1", "txtBoxDummy", False)
txtBoxDummy.AutoSize = True
Dim lRow As Long
Dim lCol As Long
Dim strColWidth As String
For lRow = 0 To ctListCtrl.ListCount - 1
For lCol = 0 To ctListCtrl.ColumnCount - 1
txtBoxDummy = ctListCtrl.List(lRow, lCol)
strColWidth = SetColWidth(strColWidth, txtBoxDummy, lCol)
Next lCol
Next lRow
ctListCtrl.ColumnWidths = strColWidth
End Function
Size Each time you add a single item
'assumes rs.Fields is a control or converted to control
Dim strColWidth As String
strColWidth = SetColWidth(strColWidth, rs.Fields(0), 0)
strColWidth = SetColWidth(strColWidth, rs.Fields(1), 1)
strColWidth = SetColWidth(strColWidth, rs.Fields(2), 2)
strColWidth = SetColWidth(strColWidth, rs.Fields(3), 3)
'etc
ctListCtrl.ColumnWidths = strColWidth
Or size once after adding lot of items
Call AutoSizeColsWidth(myListBox) 'call after completely loading listbox
Added as I was looking for a way to do this & OP is Google's top answer.
You can use the ColumnWidths property to set the size of the columns.
eg `ExistingSheetsListBox.ColumnWidths = "60;60;160;160;60"
For more info see here
I have not found anyway to automatically set the widths depending ont he data in each column, and I am pretty sure such a method does not exist.
Read the width of the existing column and assign it to a variable and use that in the listbox column property.
For Example You have six columns A to F and You need to auto fit the column F
FWidth = Columns("F").ColumnWidth * 7.6
ListBox1.ColumnWidths = "120,120,120,120,120," & FWidth & ""
The Multiply of 7.6 will converts the value to Points.
In Similar Way You can do it for all of Your columns.
Autosize Listbox and Combobox Columns with this function and Optionaly Resize Listbox/Combobox controls themselves.
Function ControlsResizeColumns(LBox As MSForms.Control, Optional ResizeListbox As Boolean)
Application.ScreenUpdating = False
Dim ws As Worksheet
If sheetExists("ListboxColumnWidth", ThisWorkbook) = False Then
Set ws = ThisWorkbook.Worksheets.Add
ws.Name = "ListboxColumnwidth"
Else
Set ws = ThisWorkbook.Worksheets("ListboxColumnwidth")
ws.Cells.Clear
End If
'---Listbox/Combobox to range-----
Dim rng As Range
Set rng = ThisWorkbook.Sheets("ListboxColumnwidth").Range("A1")
Set rng = rng.Resize(UBound(LBox.List) + 1, LBox.ColumnCount)
rng = LBox.List
rng.Characters.Font.Name = UserForm1.ListBox1.Font.Name
rng.Characters.Font.Size = UserForm1.ListBox1.Font.Size
rng.Columns.AutoFit
'---Get ColumnWidths------
rng.Columns.AutoFit
Dim sWidth As String
Dim vR() As Variant
Dim n As Integer
Dim cell As Range
For Each cell In rng.Resize(1)
n = n + 1
ReDim Preserve vR(1 To n)
vR(n) = cell.EntireColumn.Width + 10 'if not some extra space it cuts a bit off the tail
Next cell
sWidth = Join(vR, ";")
Debug.Print sWidth
'---assign ColumnWidths----
With LBox
.ColumnWidths = sWidth
'.RowSource = "A1:A3"
.BorderStyle = fmBorderStyleSingle
End With
'----Optionaly Resize Listbox/Combobox--------
If ResizeListbox = True Then
Dim w As Long
For i = LBound(vR) To UBound(vR)
w = w + vR(i)
Next
DoEvents
LBox.Width = w + 10
End If
'remove worksheet
Application.DisplayAlerts = False
ws.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Function
Function sheetExists(sheetToFind As String, Optional InWorkbook As Workbook) As Boolean
If InWorkbook Is Nothing Then Set InWorkbook = ThisWorkbook
On Error Resume Next
sheetExists = Not InWorkbook.Sheets(sheetToFind) Is Nothing
End Function

function to convert String to upper case

I have been trying to make a user defined function I wrote return it's value in all upper case, using the String.ToUpper() method in VBA. When I try to use my UDF in excel, I get a compiler error that just highlights the top line of my UDF:
Function removeSpecial(sInput As String) As String
Here is the code in it's entirety:
Function removeSpecial(sInput As String) As String
Dim sSpecialChars As String
Dim i As Long
sSpecialChars = "\/:*?™""®<>|.&## (_+`©~);-+=^$!,'" 'This is your list of characters to be removed
For i = 1 To Len(sSpecialChars)
sInput = Replace$(sInput, Mid$(sSpecialChars, i, 1), "")
Next
sInput = sInput.ToUpper()
removeSpecial = sInput
End Function
The code works fine to remove special characters, but I would like it to also convert the inputted String to upper case.
I started receiving this error when I tried to add:
sInput = sInput.ToUpper()
If this code is commented out, my UDF works, but without returning the inputted string in all Upper.
Just the wrong function. You want
sInput = UCase(sInput)
Hope that helps
Confirm the function UCase(...) is working. Here is another example "Capitalize the first letter in the 2nd column from 2nd row till the end":
Sub UpCaseMacro()
' Declare variables
Dim OldValue As String
Dim NewValue As String
Dim FirstLetter As String
Dim i As Long
' Select values
lastRow = ActiveSheet.Range("B" & Rows.Count).End(xlUp).Row
ActiveSheet.Range(Cells(2, 2), Cells(lastRow, 2)).Select
' Update data
For i = 2 To Selection.Rows.Count
If Not IsEmpty(Cells(i, 2).Value) Then
OldValue = Cells(i, 2).Value
FirstLetter = Left(Cells(i, 2).Value, 1)
NewValue = UCase(FirstLetter) & Right(OldValue, Len(OldValue) - 1)
Cells(i, 2).Value = NewValue
End If
Next i
End Sub

How to get the value between two asterisk in excel?

How to get the value between two asterisk in excel?
for example:
I have these: TXI*GS*346.32*13*SP*ON*3***103634408RT0001
I only want to get the value 346.32
i these data like this for A1:A20 how can i replace them all using VBA?
Sub useSplit()
Dim s As String
s = "TXI*GS*346.32*13*SP*ON*3***103634408RT0001"
Dim a() As String
a = Split(s, "*")
Dim i As Long
For i = 0 To UBound(a)
Debug.Print a(i)
Next
End Sub
Sub extractFirstNumber()
Set objRegEx = CreateObject("vbscript.regexp")
' Match numbers of the form 123 or 123.45
objRegEx.Pattern = "\d+(\.\d*)?"
' Iterate over the first 20 rows in first column
For i = 1 To 20
Set objMatch = objRegEx.Execute(Cells(i, 1).Value)
If objMatch.Count = 1 Then
' If there is a match, replace the cell value
Cells(i, 1).Value = objMatch(0)
End If
Next i
End Sub
Edit
Sub extractFirstAndThirdNumber()
Set objRegEx = CreateObject("vbscript.regexp")
' Match numbers of the form 123 or 123.45 between asteriks
objRegEx.Pattern = "\*(\d+(\.\d*)?)"
objRegEx.Global = True
' Iterate over the first 20 rows in first column
For i = 1 To 20
Set objMatch = objRegEx.Execute(Cells(i, 1).Value)
If objMatch.Count >= 2 Then
' If there is a match, replace the cell value
'Cells(i, 1).Value = objMatch(0)
Cells(i, 3).Value = objMatch(0).SubMatches(0)
Cells(i, 4).Value = objMatch(2).SubMatches(0)
End If
Next i
End Sub