I am trying to generate unique IDs for topics of discussion. The data will be like so:
Status ID Topic Commentary
Open FIL-1 FILM
Open FIL-2 FILM
Closed LAN-1 LANG.
Open LAN-2 LANG.
The idea is that when on a new row regardless of whether it was added above or below of the last unique ID I use VBA to find the next ID. So for example above if I were to add another row at the top with the topic LANG. then it would find that LAN-2 is the lastest ID and +1 to it to become LAN-3.
I got this working when the topics were all the same with the code below (topics were all "FIL" but now there are multiple topics):
Private Function getNextID() As String
Dim row As Integer
Dim currentID As Integer
currentID = 0
' Loop round rows
For row = MIN_ROW To MAX_ROW
' Only use rows which are not blank
If Worksheets(DISCUSS).cells(row, ID).Value <> "" Then
If Mid$(Worksheets(DISCUSS).cells(row, ID).Value, InStr(3, Worksheets(DISCUSS).cells(row, ID).Value, "-") + 1) > currentID Then
currentID = Mid$(Worksheets(DISCUSS).cells(row, ID).Value, InStr(3, Worksheets(DISCUSS).cells(row, ID).Value, "-") + 1)
End If
End If
Next row
getNextID = "FIL" & "-" & currentID + 1
End Function
Does anyone know how I can set an array with the topic abbreviations used in the ID and use the code i've already written to loop through the same process using the abbreviations in the array to get the next ID for the specific topic being added?
I adjusted the code you had to include an array like you needed, it does mean you will have to pass into your procedure the name of the topic you are requesting an ID for, this could be automated if needed but its hard to know what the bigger picture is for you project so I have left it as this: -
Private Function getNextID(ByVal StrTopic As String) As String
Static AryTopics(2, 1) As String
Dim row As Integer
Dim currentID As Integer
Dim LngCounter As Long
currentID = 0
'By having the array declared static and being a fixed size, it will only get built once
'then rememebered
If AryTopics(0, 0) = "" Then
AryTopics(0, 0) = "FILM"
AryTopics(0, 1) = "FIL"
AryTopics(1, 0) = "LANG."
AryTopics(1, 1) = "LAN"
AryTopics(2, 0) = "GEOG."
AryTopics(2, 1) = "GEO"
End If
'The topic must be passed into the proce to know what to get the ID for
'This gets the related topic code from the array
For LngCounter = 0 To UBound(AryTopics, 1)
If AryTopics(LngCounter, 0) = Trim(UCase(StrTopic)) Then
StrTopic = AryTopics(LngCounter, 1)
Exit For
End If
Next
' Loop round rows
For row = MIN_ROW To MAX_ROW
' Only use rows which are not blank
If Worksheets(DISCUSS).Cells(row, ID).Value <> "" Then
'This checks to see if the ID starts with the related topic code we care about, if it does then we keep checking
If Left(Trim(UCase(Worksheets(DISCUSS).Cells(row, ID).Value)), Len(StrTopic) + 1) = StrTopic & "-" Then
If Mid$(Worksheets(DISCUSS).Cells(row, ID).Value, InStr(3, Worksheets(DISCUSS).Cells(row, ID).Value, "-") + 1) > currentID Then
currentID = Mid$(Worksheets(DISCUSS).Cells(row, ID).Value, InStr(3, Worksheets(DISCUSS).Cells(row, ID).Value, "-") + 1)
End If
End If
End If
Next row
'Output include the topic code
getNextRiskID = StrTopic & "-" & currentID + 1
End Function
This code does the trick, except the first entry for some reason (the Evaluate Formula button shows it's working, but right at the end it replaces the value with 0).
So, manually add the first ID then run the code from row 3 to the last row of your list (you'll also need to add code to ignore empty rows).
Public Sub Test()
Dim x As Long
For x = 3 To 7
AddID ThisWorkbook.Worksheets("Sheet1").Cells(x, 2)
Next x
End Sub
Public Sub AddID(Target As Range)
'Formula using A1 style:
'=LEFT($C7,3) & "-" & COUNTIF($B$2:INDEX($B:$B,ROW()-1),LEFT($C7,3) & "*")+1
'Relative column (ID is 1 column left of Topic).
Target.FormulaR1C1 = "=LEFT(RC[1],3) & ""-"" & COUNTIF(R2C:INDEX(C,ROW()-1), LEFT(RC[1],3) & ""*"")+1"
'Absolute column (ID is column B, Topic is column C)
'Target.FormulaR1C1 = "=LEFT(RC3,3) & ""-"" & COUNTIF(R2C2:INDEX(C2,ROW()-1), LEFT(RC3,3) & ""*"")+1"
Target = Target.Value
End Sub
Related
I haven't used VBA in about 10 years until needing it this week, so my recall is not that great right now - appreciate any advice you are able to give!
I have a User form where there is a multiple selection listbox option that inserts the selected items into a single cell separated by a comma. The list referenced for the listbox has 2 columns - a GROUP and a PROJECT name.
Multiple projects can fall under the same group. I have the group column going to one cell and the project to another, but if users multi-select projects from the same group they will get the same group name repeated.
How can I adjust this to allow the group name to only appear once in a cell?
Adding grouping to Excel sheet:
For X = 0 To Me.listbox_group.ListCount - 1
If Me.listbox_group.Selected(x) Then
If varGroup = "" Then
varGroup = Me.listbox_group.List (x,0)
Else
varGroup = varGroup & ", " & Me.listbox_group.List(x,0)
End If
End If
Next x
Specifying cell location for the selection to go to:
Sheets("Data").Range("Data_Start").Offset(TargetRow, 0).Value = UCase(varGroup)
In order to get only unique values you could use a dictionary
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
For x = 0 To Me.listbox_group.ListCount - 1
If Me.listbox_group.Selected(x) Then
dict(listbox_group.List(x, 0)) = listbox_group.List(x, 0)
' If varGroup = "" Then
' varGroup = Me.listbox_group.List(x, 0)
' Else
' varGroup = varGroup & ", " & Me.listbox_group.List(x, 0)
' End If
End If
Next x
Dim s As Variant
s = Join(dict.Keys, ",")
Sheets("Data").Range("Data_Start").Offset(TargetRow, 0).Value = UCase(s)
I only assign values because there is a kind of extra feature: If the Key does not exist it automatically adds the Key and Item to the dictionary.
Upper ander lower case pitfall: The above code will a consider groups with the name G1 and g1 as different. If you do not want that use
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
For x = 0 To Me.listbox_group.ListCount - 1
If Me.listbox_group.Selected(x) Then
Dim selElement As String
selElement = UCase(listbox_group.List(x, 0))
dict(selElement) = selElement
' If varGroup = "" Then
' varGroup = Me.listbox_group.List(x, 0)
' Else
' varGroup = varGroup & ", " & Me.listbox_group.List(x, 0)
' End If
End If
Next x
Dim s As Variant
s = Join(dict.Keys, ",")
Sheets("Data").Range("Data_Start").Offset(TargetRow, 0).Value = s
I want to loop thru an array and extract its delimited values that match every date in a range. For e.g., in the picture below:
I have a date range, say 01-01 to 01-10.
I also have a list of strings (see second pic).
In the array below (see first pic), I have three different values delimited by a semi-colon.
For all matching strings (from second pic) e.g., SISBTXTRPR-(number) and date, I want to extract the last part of the array value.
Picture 1
Picture 2
So, for all array values that match "SISBTXTRPR-4649" (the string from picture 2) and a date (in this case 12-12), I want to extract "2h" from the array. The date range for each string, in this case, "SISBTXTRPR-4649" will be 10 days. I am racking my brain on how to do this :(
This is all I could come up with so far:
While i < UBound(sTimeStamp)
If StrComp(Trim(Format(Now(), "MM-DD")), Trim(Split(sTimeStamp(9), ";")(1))) = 0 And StrComp(Trim(Worksheets("KPIs").Range("AN" & iCounter)), Trim(Split(sTimeStamp(1), ";")(0))) Then
End If
i = i + 1
Wend
Link to file
Sample File
The next code will return occurrences for each string in 'Task' range matching the date from its corresponding 'sTimeStamp Array' string with the one from the 'Date Range Array'. Each occurrence will be add to the next column of 'Task' string column:
Private Sub findOccurrences()
Dim sTask As Worksheet, sStamp As Worksheet, sDate As Worksheet
Dim arrTask As Variant, arrStamp As Variant, arrDate As Variant
Dim i As Long, j As Long, arrS As Variant, El As Variant, dtRef As Date
Set sTask = ThisWorkbook.Sheets("Task")
Set sStamp = ThisWorkbook.Sheets("sTimeStamp Array")
Set sDate = ThisWorkbook.Sheets("Date Range Array")
arrTask = sTask.Range("A2:A" & sTask.Range("A" & sTask.Rows.Count).End(xlUp).Row).Value
arrStamp = sStamp.Range("A2:A" & sStamp.Range("A" & sStamp.Rows.Count).End(xlUp).Row).Value
arrDate = sDate.Range("A2:A" & sDate.Range("A" & sDate.Rows.Count).End(xlUp).Row).Value
'____________________________________________________________________________
sTask.Range("B2:K" & sTask.Range("A" & sTask.Rows.Count).End(xlUp).Row).Clear
Do While i < UBound(arrStamp)
i = i + 1
arrS = Split(arrStamp(i, 1), ";")
For j = 1 To UBound(arrTask)
If arrS(0) = arrTask(j, 1) Then
For Each El In arrDate
dtRef = DateValue(Format(El, "MM-DD"))
If dtRef = DateValue(Format(arrS(1), "MM-DD")) Then
Debug.Print arrS(0) & " (row number " & j + 1 & "), interval """ & _
El & """ exists."
sTask.Cells(j + 1, sTask.Cells(j + 1, _
sTask.Columns.Count).End(xlToLeft).Column).Offset(0, 1).Value = El
End If
Next
End If
Next j
Loop
End Sub
And the short variant working similar to your approach, finding the occurrences for Today date (if I correctly deduced what you intended to achieve), replace the looping part with this:
'______________________________________________________________________________
sStamp.Range("B2:B" & sStamp.Range("A" & sStamp.Rows.Count).End(xlUp).Row).Clear
sTask.Range("A2:A" & sTask.Range("A" & sStamp.Rows.Count).End(xlUp).Row).Interior.ColorIndex = -4142
While i < UBound(arrStamp)
i = i + 1
If StrComp(DateValue(Format(Date, "MM-DD")), DateValue(Split(arrStamp(i, 1), ";")(1))) = 0 And _
Not isMatchErr(CStr(Split(arrStamp(i, 1), ";")(1)), arrDate) Then
Debug.Print "OK for """ & Split(arrStamp(i, 1), ";")(0) & """ of row """ & i & """."
sStamp.Range("B" & i + 1).Value = "OK"
If Not isMatchErr(CStr(Split(arrStamp(i, 1), ";")(0)), arrTask) Then
rowOK = WorksheetFunction.Match(Split(arrStamp(i, 1), ";")(0), arrTask, 0) + 1
sTask.Range("A" & rowOK).Interior.ColorIndex = 3
End If
End If
Wend
And add the next function:
Function isMatchErr(strTime As String, arrDate As Variant) As Boolean
Dim k As Long
On Error Resume Next
k = WorksheetFunction.Match(strTime, arrDate, 0)
If Err.Number <> 0 Then
Err.Clear: On Error GoTo 0: isMatchErr = True
End If
On Error GoTo 0
End Function
Besides the message in Immediate Window, an "OK" will be put on column B:B for all occurrences (in 'sTimeStamp Array' sheet) and background of the matching cell (in 'Task' sheet will be colored in red... In order to do that, I added a new record and modified an existing cell, for "Today" ("01-12"). Please do the same in order to obtain at least two results in column B:B.
Please confirm that this is what you wanted. If not, please better clarify the need...
I am trying to dissect some text within VBA, the two text examples I am trying to change are below:
Original Data
FAST CASH W5600Z *Scenario 1*
FAST CASH 5786Z *Scenario 2*
Output Required
D5600Z (Replacing the "W" with a "D") *Scenario 1*
D5786Z (Adding a "D" before the first numeric character) *Scenario 2*
This is the final part of my data manipulation and the code used to manipulate the data previously can be seen in the code below:
For Each b In wbRecFile.Sheets("Corrected Data1").Range("B1:B" & Lastrow)
If b.Value <> "" Then
If UCase(Left(b.Value, 1)) = "W" Then b.Value = "D" & Right(b.Value, Len(b.Value) - 1)
GoTo nextline
End If
If IsNumeric(Left(b.Value, 1)) Then b.Value = "D" & b.Value
GoTo nextline
End If
End If
nextline:
Next b
Any suggestions on how I could achieve this within VBA would be much appreciated. I am able to complete this task in excel formulas see below but I am trying my best to avoid this as a solution.
="D"&RIGHT(MID(Cell reference,FIND("W",cell reference),6),5)
All I have used the below code to resolve my issue.
Dim bText As String
Public Sub DisectText()
Dim myString As String
myString = bText
Dim position As Long
position = GetFirstNumeric(myString)
If position > 0 Then
bText = "D" & Mid(myString, position, 5)
Else
bText = ""
End If
End Sub
Public Function GetFirstNumeric(ByVal value As String) As Long
Dim i As Long
Dim bytValue() As Byte
Dim lngRtnVal As Long
bytValue = value
For i = 0 To UBound(bytValue) Step 2
Select Case bytValue(i)
Case vbKey0 To vbKey9
If bytValue(i + 1) = 0 Then
lngRtnVal = (i \ 2) + 1
Exit For
End If
End Select
Next
GetFirstNumeric = lngRtnVal
End Function
I am concatenating every columns of the rows on excel sheet and I am already done with the concatenating of every columns.
My problem is I only need to concatenate the rows that does not contain letter T on column A starting row 3. please see image below
sample formula
=Sheet1!A3&Sheet1!B3&Sheet1!C3&Sheet1!D3&Sheet1!E3&Sheet1!F3&Sheet1!G3&Sheet1!H3&Sheet1!I3&Sheet1!J3&Sheet1!K3&Sheet1!L3&Sheet1!M3&Sheet1!N3&Sheet1
on that image, you can see the result below of the concatenated columns from the above details, but not all the time the rows to be concatenated there has the same number(like on the above image that has only 3 rows to be computed), do you know some code or formula for this matter?
You can try
=TEXTJOIN(A1:N1)
Or whatever your range should be.
But dynamically in VBA it done like this:
Dim row, col As Integer
Dim curStr As String
row = 1
col = 1
curStr = ""
Do While Sheets("Input").Cells(row, col) <> ""
Do While Sheets("Input").Cells(row, col) <> ""
curStr = curStr + Sheets("Input").Cells(row, col)
col = col + 1
Loop
col = 1
Sheets("Output").Cells(row, col) = curStr
row = row + 1
curStr = ""
Loop
Use this UDF - User Defined Function
Function conc(rangetoconc As Range) As String
Dim finalresult As String
Dim cell As Range
finalresult = vbNullString
If InStr(1, rangetoconc.Cells(1, 1), "T") = 0 Then
For Each cell In rangetoconc
If CStr(cell.Value) <> vbNullString And CStr(cell.Value) <> " " Then
finalresult = finalresult & CStr(cell.Value)
End If
Next
End If
conc = finalresult
End Function
I just figured out the answer to my question after reading some excel formula online.
=IF(A1="","",
IF(LEFT(A1,1)="T","",
IF('Sheet1'!A2<>"",'Sheet1'!A2&'Sheet1'!B2&'Sheet1'!C2&'Sheet1'!D2&'Sheet1'!E2&'Sheet1'!F2&'Sheet1'!G2&'Sheet1'!H2&'Sheet1'!I2&'Sheet1'!J2&'Sheet1'!K2&'Sheet1'!L2&'Sheet1'!M2&'Sheet1'!N2&'Sheet1'!O2&'Sheet1'!P2&'Sheet1'!Q2&'Sheet1'!R2&'Sheet1'!S2,"T "&MIN(ROW(A2:A3))+ROWS(A2)-3 & " sum of invoice "& SUM('Sheet1'!H:H))))
First bit of code resorts the order, and then refreshes the list with the new order shown in the list box.
The listbox is currently set as "Single", but I want it to be established as a "fmMultiSelectMulti". From a "Multi", I am able to gather the selected index numbers in a string with the public function call GetSelectedIndexes(userformNameHere). (i.e. "0,4,7,8,9...")
My issues is once I cut and paste the certain rows around to resort the listbox, I want to be able to show the user that they still have the same values in the listbox selected.
Public Function GetSelectedIndexes(lBox As MSForms.ListBox) As String
'returns an array of selected index numbers in a listbox
Dim tmparray() As Variant
Dim i As Integer
Dim selCount As Integer
selCount = -1
'## Iterate over each item in the ListBox control:
For i = 0 To lBox.ListCount - 1
'## Check to see if this item is selected:
If lBox.Selected(i) = True Then
'## If this item is selected, then add it to the array
selCount = selCount + 1
ReDim Preserve tmparray(selCount)
tmparray(selCount) = lBox.ListIndex
End If
Next
If selCount = -1 Then
'## If no items were selected, return an empty string
GetSelectedIndexes = "" ' or "No items selected", etc.
Else:
'## Otherwise, return the array of items as a string,
' delimited by commas
GetSelectedIndexes = Join(tmparray, ", ")
End If
End Function
here is the rest of my code which works with the single value selectable listbox:
Private Sub SpinButton1_SpinUp()
' cuts + moves UP one cell... ONLY WORKS IF FmMultiSelectSingle...
ThisWorkbook.Sheets("mon").Activate
If Not MonMissions2.ListIndex < 1 Then
selRow = MonMissions2.ListIndex + 2
Range("B" & selRow).EntireRow.Select
Selection.Cut
Selection.Offset(-1, 0).Insert Shift:=xlDown
'Reloads wing priorities list
MonMissions2.Clear
With MonMissions2
List = Range("A2:A500").Value
For i = 1 To UBound(List, 1)
If Len(Trim(List(i, 1))) > 0 Then
.AddItem Range("B" & i + 1).Value & "-" & Range("C" & i + 1).Value & "-" & Range("D" & i + 1).Value ' populate the listbox
End If
Next i
MonMissions2.ListIndex = selRow - 3
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Need to re-select previously selected ListIndex HERE
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
End With
End If
End Sub
am I going to have to create 100+ variables for my dynamic listbox items to determine whether they were selected to begin with? (listbox.selected(I) = true...)