Access Multi-Select Listbox, Items Selected String, Text not Integer - vba

I am currently working on an Access Database and I am trying to create a string of selected items in a multi-select listbox separated by commas (ex. Option 1, Option 3, Option 5). I have been able to get the program to create a list, but it is listing Integers (1,3,5) instead of the text I wrote as the primary key for the data I am using in the listbox. I have tried changing my commands as Variant instead of Integer, but it is still listing 0-based numbers.
Does anyone know a solution to this problem or a code that creates the string out of text instead of integers?
Thank You!
This is the code I have currently (OptionL is the name of my list box)
Function LbxItems() As String
'Returns a list of items in the listbox
Dim lbx As ListBox
Dim varItems As Variant
Dim varItem As Variant
Dim strReturn As String
strReturn = ""
Set lbx = Me!OptionL
varItems = lbx.ItemsSelected.Count
If varItems > 0 Then
For varItem = 0 To varItems - 1
If varItem = 0 Then
strReturn = CStr(lbx.ItemsSelected(varItem))
Else
strReturn = strReturn & "," &
CStr(lbx.ItemsSelected(varItem))
End If
Next
End If
Set lbx = Nothing
LbxItems = strReturn
End Function

I was able to use the code from the link HansUp recommended to solve the problem. (https://stackoverflow.com/a/19353316/77335) Thank you all so much!

.ItemsSelected(<some number>) does not give you what I think you're expecting. It does not give you the value from any column within that selected row.
Use ItemData if you want the value contained in the Bound Column of a selected row. So you could make changes like this to your existing code:
'strReturn = CStr(lbx.ItemsSelected(varItem))
strReturn = CStr(lbx.ItemData(lbx.ItemsSelected(varItem)))
However I would switch to a simpler approach like this:
Public Function LbxItems() As String
Dim strReturn As String
Dim varItem As Variant
With Me.OptionL
For Each varItem In .ItemsSelected
strReturn = strReturn & "," & .ItemData(varItem)
Next
End With
If Len(strReturn) > 0 Then
strReturn = Mid(strReturn, 2) ' discard leading comma
End If
LbxItems = strReturn
End Function

Related

VBA Function not Returning Value

I have a VBA code that's designed to search a CSV String and add Carriage Returns where they should exist. I've split it up into two seperate functions - one to search the string and put the index of where the CRs should go into an array and a second function to actually add the CRs.
The issue I'm running into is that the value in the immediate window/in the watch window for the functions is correct within the function itself, but it assigns the result variable a blank string.
'*****************Import CSV**********************
'Took this straight off the internet because it was reading Jet.com files as one single line
'
Sub ImportCSVFile(filepath As String)
.....
line = SearchString(line, "SALE")
.....
End Sub
'****************Search String***************************
'This is search the string for something - It will then call a function to insert carriage returns
Function SearchString(source As String, target As String) As String
Dim i As Integer
Dim k As Integer
Dim myArray() As Variant
Dim resultString As String
Do
i = i + 1
If Mid(source, i, Len(target)) = target Then
ReDim Preserve myArray(k)
myArray(k) = i
k = k + 1
End If
DoEvents
Loop Until i = Len(source)
resultString = addCarriageReturns(source, myArray) 'resultString here is assigned a blank string
SearchString = resultString
End Function
'***************Add Carraige Returns**************************
'Cycle through the indices held in the array and place carriage returns into the string
Function addCarriageReturns(source As String, myArray As Variant) As String
Dim i As Integer
Dim resultString As String
resultString = source
For i = 0 To UBound(myArray, 1)
resultString = Left(resultString, myArray(i) + i) & Chr(13) & Right(resultString, Len(resultString) - myArray(i) + i)
Next i
addCarraigeReturns = resultString 'The value of addCarriageReturn is correct in the immediate window here
End Function
In the function the value is not blank
...but when it passes it back, it says the value is blank
I'm just curious, why do you want separate functions like this?
Can you just use:
line = Replace(line, "SALE", "SALE" & Chr(13))

VBA - Split and IsError Function

I'm struggling with VBA code. I'm working on a ID code generator program. One of the processes involves Split Company Names by words, and taking the first two words. Split has proved to be useful in this tasks, however, when in dealing with Company Names shorter than 2 words I've got a #VALUE! Error.
One way I tried to fix it, was using the ISERROR function, so if I get any error it replaces it with a character, say "X".
In summary, what I'm trying is to capture only the second Word of the Name, if there is no second Word, just display "X".
Public Function idcode_2(text As String) As String
Dim Arr_text As Variant
Dim rz_x, rz2, code As String
Dim i As Integer
Dim c
Arr_text = Split(text, " ", 3)
rz2 = Arr_text(1)
If IsError(rz2) = True Then
rz2 = "X"
Else
rz2 = rz2 & ""
End If
idcode_2 = rz2
End Function
I'm using VBA in Excel - Microsoft Office Professional Plus 2013.
Arr_text will be a zero-based array - UBound(Arr_text) will give you the upper bound of that array (zero if one item, one if two items, etc)
Public Function idcode_2(text As String) As String
Dim Arr_text As Variant, rz2
Arr_text = Split(text, " ", 3)
If UBound(Arr_text ) > 0 Then
rz2 = Arr_text(1)
Else
rz2 = "x"
End If
idcode_2 = rz2
End Function
Public Function idcode_2(text As String) As String
If Instr(text, " ") > 0 Then
idcode = Split(text)(1)
Else
idcode = "x"
End If
End Function

Access VBA How can I filter a recordset based on the selections in a multi select list box?

I am trying to use the OpenForm function to filter based on the selections in a multi select list box. what is the correct syntax for this, or is there a better way to go about it? For the sake of example let's say:
List Box has options Ken, Mike, and Sandy.
Car has options Car1, Car2, and Car 3. All cars are owned by 1 or more people from that list box.
If someone from the list box is selected, I would like to open a form containing the cars owned by those people selected.
Thank you!
Ok So I figured out a way to do it:
Create a string to hold a query
Use a For loop to populate the string based on each item selected
Put that string as a filter in the OpenForm command.
Here is the specific code I used to to it. My example in the original post used Cars and People, but my actual context is different: Estimators and Division of Work are the filters. Let me know if you have any questions about it if you're someone who has the same question! Since it might be confusing without knowing more about what exactly I'm trying to accomplish.
Dim strQuery As String
Dim varItem As Variant
'query filtering for estimators and division list box selections
strQuery = ""
If Me.EstimatorList.ItemsSelected.Count + Me.DivisionList.ItemsSelected.Count > 0 Then
For Each varItem In Me.EstimatorList.ItemsSelected
strQuery = strQuery + "[EstimatorID]=" & varItem + 1 & " OR "
Next varItem
If Me.EstimatorList.ItemsSelected.Count > 0 And Me.DivisionList.ItemsSelected.Count > 0 Then
strQuery = Left(strQuery, Len(strQuery) - 4)
strQuery = strQuery + " AND "
End If
For Each varItem In Me.DivisionList.ItemsSelected
strQuery = strQuery + "[DivisionID]=" & varItem + 1 & " OR "
Next varItem
strQuery = Left(strQuery, Len(strQuery) - 4)
End If
Using the JOIN function for cleaner and safer code
When you find yourself repeatedly building incremental SQL strings with delimiters like "," "AND" "OR" it is convenient to centralize the production of array data and then use the VBA Join(array, delimiter) function.
If the interesting keys are in an array, a user selection from a multiselect listbox to build a SQL WHERE fragment for the form filter property could look like this:
Private Sub lbYear_AfterUpdate()
Dim strFilter As String
Dim selction As Variant
selction = ListboxSelectionArray(lbYear, lbYear.BoundColumn)
If Not IsEmpty(selction) Then
strFilter = "[Year] IN (" & Join(selction, ",") & ")"
End If
Me.Filter = strFilter
If Not Me.FilterOn Then Me.FilterOn = True
End Sub
A generic function to pick any column data from selected lisbok rows may look like this:
'Returns array of single column data of selected listbox rows
'Column index 1..n
'If no items selected array will be vbEmpty
Function ListboxSelectionArray(lisbox As ListBox, Optional columnindex As Integer = 1) As Variant
With lisbox
If .ItemsSelected.Count > 0 Then
Dim str() As String: ReDim str(.ItemsSelected.Count - 1)
Dim j As Integer
For j = 0 To .ItemsSelected.Count - 1
str(j) = CStr(.Column(columnindex - 1, .ItemsSelected(j)))
Next
ListboxSelectionArray = str
Else
ListboxSelectionArray = vbEmpty
End If
End With
End Function
A few array builders in the application library and coding can be made look more VB.NET

Read all unique values from cells in a range and create a comma separated string from them?

I have written the following function to read all unique values from cells in a range and create a comma separated string from them? Is there a better, simpler way to do this?
Private Sub CsvUniqueValues(r As Excel.Range)
Dim c As Excel.Range
Dim s As String = ""
For Each c In r.Cells
If ExcelApp.WorksheetFunction.CountIf(r, c.Value) = 1 Then
s = s & ","
End If
Next
If s.Length > 0 Then
s = s.Substring(0, s.Length - 1)
End If
End Sub
You could use LINQ to get a list of only the unique values, like this:
Dim uniqueValues As IEnumerable = r.Cells.Where(Function(x) ExcelApp.WorksheetFunction.CountIf(r, x.Value) = 1))
Then, you could use LINQ to convert all of those unique values to strings:
Dim uniqueStrings As IEnumerable(Of String) = uniqueValues.Select(Of String)(Function(x) x.ToString())
Then you can use LINQ to convert the resulting list to an array:
Dim uniqueArray() As String = uniqueStrings.ToArray()
Then, you could use the String.Join method to combine them into a single CSV string:
Dim csv As String = String.Join(",", uniqueArray)
You could, of course, do all of this in a single command, like this:
Dim csv As String = String.Join(",",
r.Cells.Where(Function(x) ExcelApp.WorksheetFunction.CountIf(r, x.Value) = 1))
.Select(Of String)(Function(x) x.ToString())
.ToArray())
The question, though, is whether or not you would call that "easier". LINQ is useful because it makes code easier to to read and write, but when it's taken too far, it can become less readable, thereby defeating the purpose of using it. At the very least, to make your code more clear, I would move the first part into a named function so it's more self-documenting:
Public Function GetUniqueCellValuesAsString(r As Excel.Range) As IEnumerable(Of String)
Return r.Cells.Where(
Function(x) ExcelApp.WorksheetFunction.CountIf(r, x.Value) = 1))
.Select(Of String)(Function(x) x.ToString())
End Function
Then you could just build the CSV string like this:
Dim csv As String = String.Join(",", GetUniqueCellValuesAsString(r).ToArray())
I would make use of the collection object. Since collections can only contain unique values, trying to add all of your input data to a collection will result in an array of unique values. The following modification lets CsvUniqueValues return a comma separated string from the values in any given range.
'Test function and return result in MsgBox
Sub ReturnUnique()
MsgBox CsvUniqueValues(Selection)
End Sub
'Function will return csv-string from input range
Function CsvUniqueValues(r As Range) As String
Dim Cell As Range
Dim i As Integer
Dim DistCol As New Collection
Dim s As String
'Add all distinct values to collection
On Error Resume Next
For Each Cell In r
DistCol.Add Cell.Value, Cell.Value
Next Cell
On Error GoTo 0
'Write collection to comma seperated list
For i = 1 To DistCol.Count
s = s & DistCol.Item(i) & "; "
Next i
s = Left(s, Len(s) - 2)
CsvUniqueValues = s
End Function

Excel VBA Type Mismatch (13)

I am getting a type mismatch error in VBA and I am not sure why.
The purpose of this macro is to go through a column in an Excel spreadsheet and add all the emails to an array. After each email is added to the first array, it's also supposed to added to a second array but split into two pieces at the # symbol in order to separate name from domain. Like so: person#gmail.com to person and gmail.com.
The problem that I'm getting is that when it gets to the point where it's supposed to split the email, it throws a Type Mismatch error.
Specifically this part:
strDomain = Split(strText, "#")
Here is the complete code:
Sub addContactListEmails()
Dim strEmailList() As String 'Array of emails
Dim blDimensioned As Boolean 'Is the array dimensioned?
Dim strText As String 'To temporarily hold names
Dim lngPosition As Long 'Counting
Dim strDomainList() As String
Dim strDomain As String
Dim dlDimensioned As Boolean
Dim strEmailDomain As String
Dim i As Integer
Dim countRows As Long
'countRows = Columns("E:E").SpecialCells(xlVisible).Rows.Count
countRows = Range("E:E").CurrentRegion.Rows.Count
MsgBox "The number of rows is " & countRows
'The array has not yet been dimensioned:
blDimensioned = False
Dim counter As Long
Do While counter < countRows
counter = counter + 1
' Set the string to the content of the cell
strText = Cells(counter, 5).Value
If strText <> "" Then
'Has the array been dimensioned?
If blDimensioned = True Then
'Yes, so extend the array one element large than its current upper bound.
'Without the "Preserve" keyword below, the previous elements in our array would be erased with the resizing
ReDim Preserve strEmailList(0 To UBound(strEmailList) + 1) As String
Else
'No, so dimension it and flag it as dimensioned.
ReDim strEmailList(0 To 0) As String
blDimensioned = True
End If
'Add the email to the last element in the array.
strEmailList(UBound(strEmailList)) = strText
'Also add the email to the separation array
strDomain = Split(strText, "#")
If strDomain <> "" Then
If dlDimensioned = True Then
ReDim Preserve strDomainList(0 To UBound(strDomainList) + 1) As String
Else
ReDim strDomainList(0 To 0) As String
dlDimensioned = True
End If
strDomainList(UBound(strDomainList)) = strDomain
End If
End If
Loop
'Display email addresses, TESTING ONLY!
For lngPosition = LBound(strEmailList) To UBound(strEmailList)
MsgBox strEmailList(lngPosition)
Next lngPosition
For i = LBound(strDomainList) To UBound(strDomainList)
MsgBox strDomainList(strDomain)
Next
'Erase array
'Erase strEmailList
End Sub
ReDiming arrays is a big hassle. Welcome to the world of collections and Dictionarys. Collection objects are always accessible. Dictionaries require a reference to Microsoft Scripting Runtime (Tools>References>scroll down to find that text and check the box> OK). They dynamically change size for you, you can add, remove items very easily compared to arrays, and Dictionaries especially allow you to organize your data in more logical ways.
In the below code I used a dictionary there the key is the domain (obtained with the split function). Each value for a key is a collection of email addresses with that domain.
Put a break point on End Sub and look at the contents of each of these objects in your locals window. I think you'll see they make more sense and are easier in general.
Option Explicit
Function AllEmails() As Dictionary
Dim emailListCollection As Collection
Set emailListCollection = New Collection 'you're going to like collections way better than arrays
Dim DomainEmailDictionary As Dictionary
Set DomainEmailDictionary = New Dictionary 'key value pairing. key is the domain. value is a collection of emails in that domain
Dim emailParts() As String
Dim countRows As Long
Dim EmailAddress As String
Dim strDomain As String
'countRows = Columns("E:E").SpecialCells(xlVisible).Rows.Count
Dim sht As Worksheet 'always declare your sheets!
Set sht = Sheets("Sheet1")
countRows = sht.Range("E2").End(xlDown).Row
Dim counter As Long
Do While counter < countRows
counter = counter + 1
EmailAddress = Trim(sht.Cells(counter, 5))
If EmailAddress <> "" Then
emailParts = Split(EmailAddress, "#")
If UBound(emailParts) > 0 Then
strDomain = emailParts(1)
End If
If Not DomainEmailDictionary.Exists(strDomain) Then
'if you have not already encountered this domain
DomainEmailDictionary.Add strDomain, New Collection
End If
'Add the email to the dictionary of emails organized by domain
DomainEmailDictionary(strDomain).Add EmailAddress
'Add the email to the collection of only addresses
emailListCollection.Add EmailAddress
End If
Loop
Set AllEmails = DomainEmailDictionary
End Function
and use it with
Sub RemoveUnwantedEmails()
Dim allemailsDic As Dictionary, doNotCallSheet As Worksheet, emailsSheet As Worksheet
Set doNotCallSheet = Sheets("DoNotCallList")
Set emailsSheet = Sheets("Sheet1")
Set allemailsDic = AllEmails
Dim domain As Variant, EmailAddress As Variant
Dim foundDoNotCallDomains As Range, emailAddressesToRemove As Range
For Each domain In allemailsDic.Keys
Set foundDoNotCallDomains = doNotCallSheet.Range("A:A").Find(domain)
If Not foundDoNotCallDomains Is Nothing Then
Debug.Print "domain found"
'do your removal
For Each EmailAddress In allemailsDic(domain)
Set emailAddressesToRemove = emailsSheet.Range("E:E").Find(EmailAddress)
If Not emailAddressesToRemove Is Nothing Then
emailAddressesToRemove = ""
End If
Next EmailAddress
End If
Next domain
End Sub
strDomain must store array of the split text, therefore,
Dim strDomain As Variant
Afterwards, strDomain should be referenced by index, if operations with certain fragments will be made:
If strDomain(i) <> "" Then
The split function returns an array of strings based on the provided separator.
In your if you are sure that the original string is an email, with just one "#" in it then you can safely use the below code:
strDomain = Split(strText, "#")(1)
This will get you the part after "#" which is what you are looking for.
Split returns an array:
Dim mailComp() As String
[...]
mailComp = Split(strText, "#")
strDomain = mailComp(1)
Try strDomain = Split(strText,"#")(1) to get the right hand side of the split where (0) would be the left. And of course works with more than 2 splits as well. You could dim you string variable as an array strDomain() and then Split(strText,"#") will place all the seperated text into the array.