can you suggest me a routine - algorithm in VBA that can take the following String as an input:
"A14, A22, A23, A24, A25, A33"
and turn it to this:
"A14, A22 - A25, A33"
?
Thank you
EDIT:
Thanks to #omegastripes
Sub Test()
Dim strText, strRes, strTail, i
Dim comma As String: comma = ", "
Dim dash As String: dash = "-"
Dim delimiter As String
Dim counter As Integer
strText = "A14, A22, A23, A24, A25, A26, A33, A34"
strRes = ""
strTail = ""
With CreateObject("VBScript.RegExp")
.Global = True
.Pattern = "([a-zA-Z])(\d+)"
With .Execute(strText)
strRes = .Item(0).Value
For i = 1 To .Count - 1
If (.Item(i).SubMatches(0) = .Item(i - 1).SubMatches(0)) And (.Item(i).SubMatches(1) - .Item(i - 1).SubMatches(1) = 1) Then
counter = counter + 1
If counter > 1 Then
delimiter = dash
Else
delimiter = comma
End If
strTail = delimiter & .Item(i).SubMatches(0) & .Item(i).SubMatches(1)
Else
Debug.Print "strRes: " & strRes & ", " & "strTail: " & strTail & ", " & .Item(i).SubMatches(1)
strRes = strRes & strTail & ", " & .Item(i).SubMatches(0) & .Item(i).SubMatches(1)
strTail = ""
counter = 0
End If
Next
strRes = strRes & strTail
End With
End With
MsgBox strText & vbCrLf & strRes
End Sub
this should do
Function HideValues(inputStrng As String) As String
Dim outputStrng As String, iniLetter As String, endLetter As String
Dim vals As Variant, val As Variant
Dim iVal As Long, iniVal As Long, endVal As Long, diffVal As Long
vals = Split(WorksheetFunction.Substitute(inputStrng, " ", ""), ",")
iVal = 0
Do While iVal < UBound(vals)
iniVal = getValNumber(vals(iVal), iniLetter)
endVal = getValNumber(vals(iVal + 1), endLetter)
If iniLetter = endLetter Then
diffVal = 1
Do While endVal = iniVal + diffVal And iVal < UBound(vals) - 1
diffVal = diffVal + 1
iVal = iVal + 1
endVal = getValNumber(vals(iVal + 1), endLetter)
Loop
If diffVal > 1 Then
If iVal = UBound(vals) - 1 Then If endVal = iniVal + diffVal Then iVal = iVal + 1: diffVal = diffVal + 1
outputStrng = outputStrng & vals(iVal - diffVal + 1) & " - " & vals(iVal) & ","
Else
outputStrng = outputStrng & vals(iVal) & ","
End If
Else
outputStrng = outputStrng & vals(iVal) & ","
End If
iVal = iVal + 1
Loop
If iVal = UBound(vals) Then outputStrng = outputStrng & vals(iVal) & ","
HideValues = WorksheetFunction.Substitute(Left(outputStrng, Len(outputStrng) - 1), ",", ", ")
End Function
Function getValNumber(val As Variant, letter As String) As Long
Dim strng As String
Dim i As Long
strng = CStr(val)
For i = 1 To Len(strng)
If Mid(strng, i, 1) Like "[0-9]" Then Exit For
Next i
letter = Left(strng, i - 1)
getValNumber = CLng(Right(strng, Len(strng) - i + 1))
End Function
I tested it with the following:
Sub main()
Dim inputStrng As String
inputStrng = "A21, B22, C23, D24, E25, F26"
MsgBox inputStrng & vbCrLf & vbCrLf & "becomes" & vbCrLf & vbCrLf & HideValues(inputStrng)
inputStrng = "A21, A22, A23, A24, A25, A26"
MsgBox inputStrng & vbCrLf & vbCrLf & "becomes" & vbCrLf & vbCrLf & HideValues(inputStrng)
inputStrng = "A21, A22, A23, A24, A25, A33" '
MsgBox inputStrng & vbCrLf & vbCrLf & "becomes" & vbCrLf & vbCrLf & HideValues(inputStrng)
inputStrng = "A14, A22, A23, A24, A25, A33"
MsgBox inputStrng & vbCrLf & vbCrLf & "becomes" & vbCrLf & vbCrLf & HideValues(inputStrng)
inputStrng = "A14, A22, A23, A24, A25, A26"
MsgBox inputStrng & vbCrLf & vbCrLf & "becomes" & vbCrLf & vbCrLf & HideValues(inputStrng)
End Sub
Here is an example showing how you can hide sequential values with regex:
Option Explicit
Sub Test()
Dim strText, strRes, strTail, i
strText = "A14, A22, A23, A24, A25, A33"
strRes = ""
strTail = ""
With CreateObject("VBScript.RegExp")
.Global = True
.Pattern = "([a-zA-Z])(\d+)"
With .Execute(strText)
strRes = .Item(0).Value
For i = 1 To .Count - 1
If (.Item(i).SubMatches(0) = .Item(i - 1).SubMatches(0)) And (.Item(i).SubMatches(1) - .Item(i - 1).SubMatches(1) = 1) Then
strTail = "-" & .Item(i).SubMatches(0) & .Item(i).SubMatches(1)
Else
strRes = strRes & strTail & ", " & .Item(i).SubMatches(0) & .Item(i).SubMatches(1)
strTail = ""
End If
Next
strRes = strRes & strTail
End With
End With
MsgBox strText & vbCrLf & strRes
End Sub
And the output:
Roughly you can do it like this.
Sub Way()
Dim str1 As String
Dim cet As variant
Dim str2 As String
str1 = "A14, A22, A23, A24, A25, A33"
cet = split(str1, ",")
if len(join(cet)) > 2 then
str2 = cet(0) & "," & cet(1) & "-" & cet(Ubound(cet)-1) & "," & cet(ubound(cet))
End if
debug.Print str2
End Sub
Related
My full code is as below:
FirstRow = Columns("B").Find("B").MergeArea.Row
LastRow = Columns("B").Find("B").MergeArea.Row + Columns("B").Find("B").MergeArea.Rows.Count - 1
FirstRow2 = Columns("B").Find("N").MergeArea.Row
LastRow2 = Columns("B").Find("N").MergeArea.Row + Columns("B").Find("N").MergeArea.Rows.Count - 1
Range("A" & LastRow + 1).EntireRow.Insert
Range("A" & LastRow2 + 2).EntireRow.Insert
Range("Q" & LastRow + 1) = 50
Range("Q" & LastRow2 + 2) = 100
Dim Col, ColArr, CalcRow, CalcRowArr, FRow, FirstRowArr, LRow, LastRowArr
ColArr = Array("R", "W", "Y")
CalcRowArr = Array(LastRow + 1, LastRow2 + 2)
FirstRowArr = Array(FirstRow, FirstRow2 + 1)
LastRowArr = Array(LastRow, LastRow2 + 1)
For Each Col In ColArr
For Each CalcRow In CalcRowArr
For Each FRow In FirstRowArr
For Each LRow In LastRowArr
Range(Col & CalcRow).Formula = "=SUM(" & Col & FRow & ":" & Col & LRow & ")"
Next LRow
Next FRow
Next CalcRow
Next Col
Basically I am trying to convert these 6 lines of code:
Range("R" & LastRow + 1).Formula = "=SUM(R" & FirstRow & ":R" & LastRow & ")"
Range("W" & LastRow + 1).Formula = "=SUM(W" & FirstRow & ":W" & LastRow & ")"
Range("Y" & LastRow + 1).Formula = "=SUM(Y" & FirstRow & ":Y" & LastRow & ")"
Range("R" & LastRow2 + 2).Formula = "=SUM(R" & FirstRow2 + 1 & ":R" & LastRow2 + 1 & ")"
Range("W" & LastRow2 + 2).Formula = "=SUM(W" & FirstRow2 + 1 & ":W" & LastRow2 + 1 & ")"
Range("Y" & LastRow2 + 2).Formula = "=SUM(Y" & FirstRow2 + 1 & ":Y" & LastRow2 + 1 & ")"
into the array loop in my code above:
Dim Col, ColArr, CalcRow, CalcRowArr, FRow, FirstRowArr, LRow, LastRowArr
ColArr = Array("R", "W", "Y")
CalcRowArr = Array(LastRow + 1, LastRow2 + 2)
FirstRowArr = Array(FirstRow, FirstRow2 + 1)
LastRowArr = Array(LastRow, LastRow2 + 1)
For Each Col In ColArr
For Each CalcRow In CalcRowArr
For Each FRow In FirstRowArr
For Each LRow In LastRowArr
Range(Col & CalcRow).Formula = "=SUM(" & Col & FRow & ":" & Col & LRow & ")"
Next LRow
Next FRow
Next CalcRow
Next Col
However, my end result is skipping over the first items (FirstRow and LastRow) in FirstRowArr and LastRowArr respectively, meaning that my first set of 3 values ("R", "W", "Y" & LastRow + 1) becomes a duplicate of my second set of 3 values ("R", "W", "Y" & LastRow2 + 2).
Per my earlier comment, you only really want two loops:
For Each Col In ColArr
Dim n as long
For n = lbound(calcrowarr) to ubound(calcrowarr)
range(col & calcrowarr(n)).formula = "=SUM(" & Col & FirstRowArr(n) & ":" & Col & LastRowArr(n) & ")"
next n
Next Col
Try this .. Edited your code a little. Instead of arrays and loops you can insert the formula in 4 lines starting from CR = LR + 1 using relative referenced formula (R1C1)
Sub Macro1()
Dim FR, LR, FR2, LR2, CR, CR2
FR = Columns("B").Find("B").MergeArea.Row
LR = Columns("B").Find("B").MergeArea.Row + _
Columns("B").Find("B").MergeArea.Rows.Count - 1
FR2 = Columns("B").Find("N").MergeArea.Row
LR2 = Columns("B").Find("N").MergeArea.Row + _
Columns("B").Find("N").MergeArea.Rows.Count - 1
Range("A" & LR + 1).EntireRow.Insert
Range("A" & LR2 + 2).EntireRow.Insert
Range("Q" & LR + 1) = 50
Range("Q" & LR2 + 2) = 100
CR = LR + 1
Range("R" & CR & ",W" & CR & ",Y" & CR).FormulaR1C1 = _
"=SUM(R[" & FR - CR & "]C:R[" & LR - CR & "]C)"
CR2 = LR2 + 2
Range("R" & CR2 & ",W" & CR2 & ",Y" & CR2).FormulaR1C1 = _
"=SUM(R[" & FR2 - CR2 & "]C:R[" & LR2 - CR2 & "]C)"
End Sub
Edit .. Added as per comment below.
Sub Macro1()
Dim findArr, FR, LR, CR, i As Long
findArr = Array("B", "N") 'Zero based array
For i = LBound(findArr) To UBound(findArr)
FR = Columns("B").Find(findArr(i)).MergeArea.Row
LR = Columns("B").Find(findArr(i)).MergeArea.Row + _
Columns("B").Find(findArr(i)).MergeArea.Rows.Count - 1
Range("A" & LR + 1).EntireRow.Insert
Range("Q" & LR + 1) = 50 * (i + 1)
CR = LR + 1
Range("R" & CR & ",W" & CR & ",Y" & CR).FormulaR1C1 = _
"=SUM(R[" & FR - CR & "]C:R[" & LR - CR & "]C)"
Next i
End Sub
I have the same line Repeating many times with small changes. i like to shorten it by using an array of objects
For example, instead of this code:
StartUpdateStr = "Update tblAfterSale SET "
EndUpdateStr = " WHERE IDAfterSale = "
IDAfterSale = Me.lblIDAfterSale.Caption
db.Execute StartUpdateStr & "Data1 = " & Me.Lable1.Caption & EndUpdateStr & IDAfterSale
db.Execute StartUpdateStr & "Data2 = " & Me.Lable2.Caption & EndUpdateStr & IDAfterSale
db.Execute StartUpdateStr & "Data3 = " & Me.Lable3.Caption & EndUpdateStr & IDAfterSale
db.Close
I'm looking for something like this:
Const dCaption = "Me.Lable1.Caption,Me.Lable2.Caption,Me.Lable3.Caption"
Public d(2) As Integer
Public Sub MyMacro()
Dim vntTemp As Variant
Dim intIndex As Integer
vntTemp = Split(lCaption, "d")
For intIndex = 0 To 2
db.Execute StartUpdateStr & "Data"& intIndex & " = " & d(intIndex) & EndUpdateStr & IDAfterSale
Next
End Sub
Can someone write me the right syntax?
Thank you
You can simply access the labels by name with Me("Label" & i)
For intIndex = 0 To 2
db.Execute StartUpdateStr & "Data" & intIndex & " = " _
& Me("Label" & (intIndex + 1)).Caption _
& EndUpdateStr & IDAfterSale
Next
I suppose you will be adding many labels in future. So can you use the below code
Private Sub PrintAllLabel()
For Each ctl In Me.Controls
If TypeName(ctl) = "Label" Then
db.Execute StartUpdateStr & "Data" & intIndex & " = " _
& ctl.Caption _
& EndUpdateStr & IDAfterSale
End If
Next ctl
End Sub
I'm trying to imitate copying multiple sheets to a new workbook and this is fine if I literally use the sheet names in the array function.
However if I try to pass a string variable into the array I get a subscript out of range error.
The line of concern is:
Wb.Sheets(Array(SheetsArray)).Copy After:=RegionWb.Sheets(RegionWb.Sheets.count)
Please see my code below :
Sub CreateFiles()
Application.ScreenUpdating = False
Dim LastRow As Long
Dim WKC As String: WKC = Replace(DateValue(DateAdd("ww", -1, Now() - (Weekday(Now(), vbMonday) - 1))), "/", ".")
Dim FilePath As String: FilePath = "Z:\MI\Krishn\Retail"
Dim BuyerLastRow As Long
Dim Wb As Workbook: Set Wb = ActiveWorkbook
Dim RegionWb As Workbook
Dim RegionCount As Integer
Dim RegionCounter As Integer
Dim SheetsArray As String
With BuyerList
LastRow = .Range("G1048576").End(xlUp).Row
BuyerLastRow = .Range("A1048576").End(xlUp).Row
'Create WKC Dir
If Dir(FilePath & "\" & WKC, vbDirectory) = "" Then
MkDir FilePath & "\" & WKC
End If
'Create Create Files
If CountFiles(FilePath & "\" & WKC) = 0 Then
For i = 2 To LastRow
RegionCounter = 0
SheetsArray = ""
' Set RegionWb = Workbooks.Add
' 'wb.SaveAs FilePath & "\" & WKC & "\" & .Cells(i, 7).Value
' RegionWb.SaveAs FilePath & "\" & WKC & "\" & "WKC " & WKC & " - " & .Cells(i, 7).Value & ".xlsb", 50
For j = 2 To BuyerLastRow
RegionCount = Application.WorksheetFunction.CountIf(.Range("C:C"), .Cells(i, 7).Value)
If .Cells(i, 7).Value = .Cells(j, 3).Value Then
SheetsArray = SheetsArray & """" & .Cells(j, 2).Value & ""","
RegionCounter = RegionCounter + 1
If RegionCounter = RegionCount Then
Debug.Print Left(SheetsArray, Len(SheetsArray) - 1)
Set RegionWb = Workbooks.Add
RegionWb.SaveAs FilePath & "\" & WKC & "\" & "WKC " & WKC & " - " & .Cells(i, 7).Value & ".xlsb", 50
'Wb.Sheets(Array(Left(SheetsArray, Len(SheetsArray) - 1))).Copy After:=RegionWb.Sheets(RegionWb.Sheets.count)
SheetsArray = Left(SheetsArray, Len(SheetsArray) - 1)
Wb.Sheets(Array(SheetsArray)).Copy After:=RegionWb.Sheets(RegionWb.Sheets.count)
'Wb.Sheets(Array()).Copy After:=RegionWb.Sheets(RegionWb.Sheets.count)
RegionWb.Save
RegionWb.Close
Exit For
End If
' Wb.Sheets(Wb.Sheets("Buyer list").Range(Cells(j, 2).Address).Value).Copy After:=RegionWb.Sheets(RegionWb.Sheets.count)
End If
Next j
'
'
Next i
End If
End With
Application.ScreenUpdating = True
End Sub
You can split the string into an Array like this:
Wb.Sheets(Split(SheetsArray, ",")).Copy After:=RegionWb.Sheets(RegionWb.Sheets.Count)
As GSerg pointed out: You'll need to remove the quotes around the Worksheet names.
SheetsArray = SheetsArray & .Cells(j, 2).Value & ","
The backslash would be a safer delimiter that using a comma because Worksheet names can include a comma but not a backslash.
SheetsArray = SheetsArray & .Cells(j, 2).Value & "/"
Wb.Sheets(Split(SheetsArray, "/")).Copy After:=RegionWb.Sheets(RegionWb.Sheets.Count)
Ok my problem here is that I'm getting a Variable is undefined: 'objObject' error at line 39 char 4. All is good if I remove lines 39-46 but the purpose of the code is to reformat the output from echoing the object which looks like this z:\\\\BAIBOA\\test.txt and change it into a string that looks like this z:\BAIBOA\test.txt to be used later in the code. This code has been edited from another source so maybe I'm not fully understanding what's going on. Any help would be greatly appreciated.
Option Explicit
Dim arrFolders, strComputer, objWMIService, strFolder, strCommand
Dim i, strQuery, strNewFile, arrNewFile, strFilePath, strTempFilePath
Dim colMonitoredEvents, strQueryFolder
arrFolders = Array("Z:\\\\test1", "Z:\\\\test2", "Z:\\\\test2")
strComputer = "."
'strQueryFolder = Replace(strFolder, "\", "\\\\")
Set objWMIService = GetObject("winmgmts:\\" & strComputer _
& "\root\CIMV2")
'Loop through the array of folders setting up the monitor for Each
i = 0
For Each strFolder In arrFolders
'Create the event sink
strCommand = "Set EventSink" & i & " = WScript.CreateObject" & _
"(""WbemScripting.SWbemSink"", ""SINK" & i & "_"")"
ExecuteGlobal strCommand
'Setup Notification
strQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 10 " & _
"WHERE Targetinstance ISA 'CIM_DirectoryContainsFile'" & _
" and TargetInstance.GroupComponent = " & _
"'Win32_Directory.Name=""" & strFolder & """'"
strCommand = "objWMIservice.ExecNotificationQueryAsync EventSink" & _
i & ", strQuery"
ExecuteGlobal strCommand
'Create the OnObjectReady Sub
strCommand = "Sub SINK" & i & "_OnObjectReady(objObject, " & _
"objAsyncContext)" & VbCrLf & vbTab & _
"Wscript.Echo objObject.TargetInstance.PartComponent" & _
VbCrLf & "End Sub"
'WScript.Echo strCommand
ExecuteGlobal strCommand
i = i + 1
---> Line 39 Set objLatestEvent = objObject.TargetInstance.PartComponent
strNewFile = objLatestEvent.TargetInstance.PartComponent
arrNewFile = Split(strNewFile, "=")
strFilePath = arrNewFile(1)
strFilePath = Replace(strFilePath, "\\", "\")
strFilePath = Replace(strFilePath, Chr(34), "")
strFileName = Replace(strFilePath, strFolder, "")
'strTempFilePath = WScript.CreateObject("Scripting.FileSystemObject").GetSpecialFolder(2) & "\TEMP.M4A"
Wscript.Echo strFilePath
Next
WScript.Echo "Waiting for events..."
i = 0
While (True)
Wscript.Sleep(1000)
Wend
Ok Ive came up with a solution for the output but now im stuck getting the script to increment correctly and will only goto the next folder if the first folder gets a file. What i need is for it to scan the array for new files not 1 by 1. :( any suggestions
Option Explicit
Dim arrFolders, strComputer, objWMIService, strFolder, strCommand
Dim i, strQuery, strNewFile, arrNewFile, strFilePath, strTempFilePath
Dim colMonitoredEvents, strQueryFolder, objObject, objLatestEvent
Dim strFileName
arrFolders = Array("Z:\\\\test1", "Z:\\\\test2", "Z:\\\\test2")
strComputer = "."
i = 0
For Each strFolder In arrFolders
'trQueryFolder = Replace(strFolder, "\", "\\\\")
strQueryFolder = strFolder
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colMonitoredEvents = objWMIService.ExecNotificationQuery ("SELECT * FROM __InstanceCreationEvent WITHIN 10 " & " WHERE Targetinstance ISA 'CIM_DirectoryContainsFile' and TargetInstance.GroupComponent='Win32_Directory.Name=""" & strQueryFolder & """'")
Wscript.Echo strQueryFolder
'Do
Set objLatestEvent = colMonitoredEvents.NextEvent
strNewFile = objLatestEvent.TargetInstance.PartComponent
arrNewFile = Split(strNewFile, "=")
strFilePath = arrNewFile(1)
strFilePath = Replace(strFilePath, "\\", "\")
strFilePath = Replace(strFilePath, Chr(34), "")
strFileName = Replace(strFilePath, strFolder, "")
strTempFilePath = WScript.CreateObject("Scripting.FileSystemObject").GetSpecialFolder(2) & "\TEMP.M4A"
' DO THE OPERATION STUFF
' ...
'Wscript.Echo objLatestEvent.TargetInstance.PartComponent
Wscript.Echo strFilePath
' If strFileName = strQueryFolder then i = i + 1 Else
'Loop
i = i + 1
Next
'WScript.Echo "Waiting for events..."
i = 0
While (True)
Wscript.Sleep(1000)
Wend
I have an array with a unknown number of elements.
I am trying to find out how can I insert all the array elements into the body of the e-mail that I'll send.
Is there a way I can reference all items of an array ( without knowing how many elements exist) ?
My code is below
Dim MyArray() As String
Dim Msg As Object
Dim item As Object
Set olApp = GetObject(, "Outlook.Application")
Set olNs = olApp.GetNamespace("MAPI")
Set olFldr = olNs.GetDefaultFolder(olFolderInbox)
Set olItms = olFldr.Items
olItms.Sort "Received", False 'False = Ascending = Older to newer
i = 0
For Each Msg In olItms
If Msg.Class = olMail Then
If InStr(1, Msg.Subject, "1401001LS") > 0 Then
ReDim Preserve MyArray(i)
If i = 0 Then
MyArray(i) = "From: " & Msg.Sender & vbNewLine & "Sent: " & Msg.SentOn & vbNewLine & "To: " & Msg.To & vbNewLine & "CC: " & Msg.CC & vbNewLine & "Subject: " & Msg.Subject & vbNewLine & vbNewLine & Msg.Body
End If
If i > 0 Then
MyArray(i) = "From: " & Msg.Sender & vbNewLine & "Sent: " & Msg.SentOn & vbNewLine & "To: " & Msg.To & vbNewLine & "CC: " & Msg.CC & vbNewLine & "Subject: " & Msg.Subject & vbNewLine & vbNewLine & Split(Msg.Body, "From: ")(0)
End If
i = i + 1
End If
End If
Next Msg
Unload Me
Done.Show
End Sub
you can loop through the elements in the array using the method below
Dim sContentsOfArray as string
Dim iCnt As Integer
For iCnt = 0 To UBound(MyArray) Step 1
'access the element at position iCnt and put it at the end of the string
sContentsOfArray = sContentsOfArray + MyArray (iCnt)
Next iCnt