I've a problem who drive me crazy.
I'm working in a VBA script with Access, and i need to save in a variable the value of list box.
At the moment was able just save the the item list order (starting from 0), but i want to save the highlighted line value instead.
how I can reach this?
Thank you so much,
Regards,
Riccardo
You will need to loop to get the selected items.
Dim item As Variant
For Each item In YourListBox.ItemsSelected
MsgBox YourListBox.ItemData(item)
Next
To store the selected items in an array:
Dim arr() As Variant, item As Variant
With YourListBox
ReDim arr(.ItemsSelected.Count - 1)
For Each item In .ItemsSelected
arr(item - 1) = .ItemData(item)
Next
End With
You can also flatten the array with the Join function.
Dim s As String
s = Join(arr, ",")
Debug.Print s
Related
I am trying to use Collection to retrieve unique items in a range.
The code works well with string items. But if I replace the string with numeric items, using the same code, the collection is empty after run. No items added. No errors found.
Would very much appreciate any help!!!
Sub unique_val()
Dim unique As New Collection
Dim cell As Range
Dim i As Integer
'Updating collection items
On Error Resume Next 'in order to ignore key repeating
For Each cell In Sheet1.Range("a1:c9")
unique.Add cell.Value, cell.Value
Next
On Error GoTo 0
'Print collection items to sheet
For i = 1 To unique.Count
Cells(i, 5) = unique(i)
Next i
End Sub
Example of data that the code works on:
Data that code doesnt work:
So I have this spreadsheet with several listboxes. In these listboxes I have some values/items that are actually filters. I want to get each item/filter of each listboxes to amend an SQL query in my code.
So I've been asked to looped through the listboxes and I managed to do it by looping the Shapes of the spreadsheet but eventually ... those listboxes are now viewed as Shapes in VBA and not listboxes anymore. I'm looking for a way to either turn my shape in listbox or maybe find a method from the Shapes type to loop each listbox's items. Here is the part of my code, so far I loop through each shapes/listboxes, if within my shapes'name there is the word "CFRA" then I want to loop within each item selected of my listbox so that my function return them.
Private Function getListFilters() As String
My_Sheet.Activate
Dim Shp
For Each Shp In My_Sheet.Shapes
pos = InStrRev(Shp.Name, "CFRA", , vbTextCompare)
MsgBox (pos)
If pos <> 0 Then
MsgBox (TypeName(Shp))
End If
Next
End Function
Thanks in advance for those who are willing to help me and have a great day :)
Since you do not explain what is to be extracted from the list box, try the next Function, please. It will deliver the list box object having "CFRA" string in its name. Of course, any string can be used:
Private Function getListObjX(strPartName As String, sh As Worksheet) As MSForms.ListBox
Dim oObj As OLEObject
For Each oObj In sh.OLEObjects
If oObj.Name Like "*" & strPartName & "*" Then
'Debug.Print oObj.Name, TypeName(oObj.Object): Stop
If TypeName(oObj.Object) = "ListBox" Then
Set getListObjX = oObj.Object: Exit Function
End If
End If
Next
End Function
It can be called in the next way:
Sub testGetListObj()
Dim sh As Worksheet, lstB As MSForms.ListBox, lstBF As ListBox
Dim i As Long, arrSel As Variant, k As Long
Set sh = ActiveSheet
Set lstB = getListObjX("CFRA", sh)
If lstB Is Nothing Then MsgBox "No such an ActiveX list box...": Exit Sub
ReDim arrSel(lstB.ListCount - 1)
For i = 0 To lstB.ListCount - 1
If lstB.Selected(i) Then
'Debug.Print lstB.List(i)
arrSel(k) = lstB.List(i): k = k + 1
End If
Next i
ReDim Preserve arrSel(k - 1)
MsgBox Join(arrSel, "|")
End Sub
But, being an ActiveX list box type, you can simply use one of its events. Of course, if you do not need to take items from more then a list box...
I also prepared a function to return the object for a Form list box (before you clarify the issue). Maybe, somebody else will use it...
Dim oObj As ListBox
For Each oObj In sh.ListBoxes 'even not being shown by intellisense, this collection exists...
If oObj.Name Like "*" & strPartName & "*" Then
'Debug.Print oObj.Name
Set getListObjF = oObj: Exit Function
End If
Next
End Function
It can be called similarly, but the lstB should be declared As ListBox.
Edited, to make the function working in one step:
Private Function getListFilters(strPartName) As String
Dim sh As Worksheet, lstB As MSForms.ListBox
Dim oObj As OLEObject, i As Long, arrSel As Variant, k As Long
Set sh = ActiveSheet ' use here your sheet
For Each oObj In sh.OLEObjects
If oObj.Name Like "*" & strPartName & "*" Then
If TypeName(oObj.Object) = "ListBox" Then
Set lstB = oObj.Object: Exit For
End If
End If
Next
If lstB Is Nothing Then MsgBox "No such an ActiveX list box...": Exit Function
ReDim arrSel(lstB.ListCount - 1)
For i = 0 To lstB.ListCount - 1
If lstB.Selected(i) Then
arrSel(k) = lstB.List(i): k = k + 1
End If
Next i
ReDim Preserve arrSel(k - 1)
getListFilters = Join(arrSel, "|")
End Function
And the function will be simple called as:
Debug.Print getListFilters("CFRA")
You access ActiveX-Objects via the OLEObjects-Collection of a worksheet. The interesting control information are in the property Object of such an object:
Use VBA function TypeName to figure out what kind of OLE object you have
Number of items can be fetched with the Object.ListCount property.
To access the items of a listbox, loop over the Object.list property (index starts at 0, so loop must run from 0 to ListCount-1)
To check if an item is selected, use the matching .Object.Selected property.
The following code will loop will print all selected items of all listboxes of a worksheet:
Sub listBoxes()
Dim objx As OLEObject
For Each objx In ActiveSheet.OLEObjects
Debug.Print "Name = " & objx.Name & " Typ = " & TypeName(objx.Object)
If TypeName(objx.Object) = "ListBox" Then
Dim i As Long
For i = 0 To objx.Object.ListCount - 1
If objx.Object.Selected(i) Then
Debug.Print objx.Name, objx.Object.list(i)
End If
Next i
End If
Next
End Sub
Update: To show the coherence between Shapes, OleObjects and ActiceX controls on a sheet:
A Shape is a container for everything that is not part of a cell/range. Could be any kind of painted shape forms (rectangels, arrows, stars...), could be an image, a chart, an OLEObject, a form control and so on.
An OLEObject is a something that is not from Excel but is put into an Excel sheet, using a technique called OLE, Object Linking and Embedding.
An ActiveX is a control (editbox, listbox...). These controls where developed by Microsoft and where meant to run in different environments (eg a browser). They are accessible via dll and this dll is added into Excel and other office programs.
Every ActiveX-Control is added as an OLEObject into a sheet, but you can have also different OLEObjects (eg an embedded Word document) that are not an ActiceX objects.
When you want to access those things via VBA, you can use the Shapes-collection that lists all shapes of a sheet (including all OLEObjects), or you can use the OLEObjects-collection that lists all OLEObjects (including all ActiveX controls). However, there is no ActiveX collection, so if you want to fetch all ActiceX-Controls, you have to loop over either the two collections mentioned above.
If you want to access an OLEObject from the shape collection, you first need to check the type of the shape, it must have the type msoOLEControlObject (12) or msoEmbeddedOLEObject (7). A list of all shape types can be found here.
If the shape is either 7 or 12, you can access the OLEObject using Shape.OLEFormat.Object. The following to loops results in exactly the same (ws is just a worksheet variable)
Dim sh As Shape, oleObj As OLEObject
For Each sh In ws.Shapes
If sh.Type = msoOLEControlObject Or sh.Type = msoEmbeddedOLEObject Then
Set oleObj = sh.OLEFormat.Object
Debug.Print oleObj.Name, oleObj.OLEType
End If
Next
For Each oleObj In ws.OLEObjects
Debug.Print oleObj.Name, oleObj.OLEType
Next
Note that sh.Name and sh.OLEFormat.Object.Name are not necessarily the same.
Now the last step is to find the ActiveX-Controls of a specific type, this was already shown in the code of the original answer above - the ActiveX-control can be accessed via oleObj.object. Check the object type if the VBA function TypeName to filter out for example your listboxes.
guys I hope someone can help me with this one.
I have a combo box that has data from a named range and I would like to select a value from the combo box and add it to the list box.
Currently I can add an item into the list box with a button but once I add another it overwrites the current item.
Also It needs to be able to add an item at the bottom if the list box already has some values in it.
I think it has something to do with finding the last row but I'm not sure, any help would be highly appreciated :)
image of the issue
Dim i As Integer
With Me.lb_lease
.ColumnCount = 3
.ColumnWidths = "200;50;50"
.AddItem
.List(i, 0) = cbox_hardware.Column(0)
.List(i, 1) = cbox_hardware.Column(1)
.List(i, 2) = cbox_hardware.Column(2)
i = i + 1
End With
I suggest to separate the actions of setting up the listbox and adding items to it. The procedure below will set up the box and clear all existing content. Change the names of the worksheet and the Listbox to match your circumstances. The code will also work if the listbox is in a userform.
Private Sub ResetListBox()
With Worksheets("LibraryAccount").ListBox1
.ColumnCount = 3
.ColumnWidths = "80;50;50"
.Clear ' delete current list
End With
End Sub
The next procedure adds an item to it. It requires a semi-colon separated string, like "One;Two;Three". You might concatenate it from your combobox result using ListIndex to identify the row. The procedure will disassemble the string and add it at the bottom of the list. Worksheet and ListBox names must be changed.
Private Sub AddToListBox(AddArray As String)
Dim Arr() As String
Dim i As Integer
Dim C As Long
Arr = Split(AddArray, ";")
With Worksheets("LibraryAccount").ListBox1
i = .ListCount
.AddItem
For C = 0 To 2
.List(i, C) = Arr(C)
Next C
End With
End Sub
The procedure below is for testing the above procedure. You can run ResetListbox and then call TestAdd several times.
Private Sub TestAdd()
AddToListBox "One;Two;Three"
End Sub
In Microsoft Visio Professional 2010 I've isolated the error I've been getting to this little code snippet. On the page is a container holding 2 shapes and I want to iterate through those shapes within another loop. But I keep getting an invalid parameter error.
My attempt at a solution is the top block, but it only works with the same definition for the inner loop. It seems like something is changing during the 2nd iteration of the outer loop, but I'm not sure. I feel it has to do with the way a For Each loop is defined.
Sub Nested_Loop_Error()
Dim a As Variant
Dim b As Variant
Dim lngs() As Long
'This Works
lngs = ActiveDocument.Pages(1).Shapes.ItemFromID(1).ContainerProperties.GetMemberShapes(visContainerFlagsDefault)
For a = 0 To 1
For Each b In lngs
'Do nothing
Next b
Next a
'This does not work
For a = 0 To 1
For Each b In ActiveDocument.Pages(1).Shapes.ItemFromID(1).ContainerProperties.GetMemberShapes(visContainerFlagsDefault)
MsgBox "In Loop for a=" & a
Next b
Next a
End Sub
Edit:
I've been playing around with it and got it to work, but what I'm really interested in is why it works. The 2nd block of code fails when a=1, giving an invalid parameter in the line docMyDoc.Pages...
The following is the code showing the difference of using a variant or a document variable to define the ActiveDocument within the loop. Using the debugger I can't see a difference in how docMyDoc or varMyDoc are defined.
Sub Nested_Loop_Error2()
Dim a As Variant
Dim b As Variant
Dim docMyDoc As Visio.Document
Dim varMyDoc As Variant
'This works
For a = 0 To 1
Set varMyDoc = ActiveDocument
For Each b In varMyDoc.Pages(1).Shapes.ItemFromID(1).ContainerProperties.GetMemberShapes(visContainerFlagsDefault)
MsgBox "Using variant, a=" & a
Next b
Next a
'This does not work
For a = 0 To 1
Set docMyDoc = ActiveDocument
For Each b In docMyDoc.Pages(1).Shapes.ItemFromID(1).ContainerProperties.GetMemberShapes(visContainerFlagsDefault)
MsgBox "Using document, a=" & a
Next b
Next a
End Sub
Using the Variant type doesn't help the compiler much: The variable called "b" should be of type Long, and the "a" variable of type Integer.
This said, you're not using the "a" variable but to repeat twice what you do in the inner loop (Msgbox), but nothing else changes.
Moreover, you need to reference the shape whose ID is b, that you're not doing.
And another tip: don't name variables after their type, but after their semantics.
I think that what you intended to do is something like the example in GetMemberShapes method's reference in MSDN:
Sub Nested_Loop()
Dim lngMemberID as Long
Dim vsoShape as Visio.Shape
Dim j as Integer
For j = 0 to 1
For Each lngMemberID In ActiveDocument.Pages(1).Shapes(1).ContainerProperties.GetMemberShapes(visContainerFlagsDefault)
Set vsoShape = ActivePage.Shapes.ItemFromID(memberID)
Debug.Print vsoShape.ID
Next lngMemberID
Next j
End Sub
Here, your vsoShape variable will refer first to one, then to the other of your shapes. And it will work even if you have more shapes in your page.
That's the good thing of Collections and the For Each loop: Collections are special objects made up as a list of other objects. They have their own methods, as Item, or Count, and shortcuts, like using a number between parenthesis to retrieve an individual object from the collection (as in Pages(1)).
What you do with For Each is to iterate through all the objects in the collection (or all the values in an array).
For your purposes, I'd try the following general structure:
dim oPage as Visio.Page
dim oShape as Visio.Shape
dim oInnerShape as Visio.Shape
For each oPage In ActiveDocument.Pages
For each oShape in oPage.Shapes
If oShape.Master.Name = "xxx" Then ' You can check the type of the shape
For each oInnerShape In oShape
' set and compute width and height
Next oInnerShape
' set and compute width and height of the containing shape
End If
Next oShape
' Rearrange shapes
Next oPage
You can construct an array storing the shape IDs, width and height, while iterating through the shapes, then use that array to rearrange the shapes.
Regards,
I don't have Visio on my computer but are you certain that the first nested loop worked?
I have doubt in lngs = ActiveDocument.Pages(1)... with Dim lngs() As Long:
Excel VBA will throw "Type mismatch" error with trying to store arr = Array(1,2) with Dim arr() As Long. Better off Dim lngs As Variant even if you know it's an array of Long being returned.
The second nested loop works in theory.
I have added a ListBox to a SHEET (not to a "UserForm")
I did this using the mouse.
I clicked the little Hammer and Wrench icon.
This ListBox seems to be easily referenced using code such as this:
ListBox1.Clear
or
ListBox1.AddItem("An option")
However, I have three of these ListBoxes (named, conveniently, ListBox1, ListBox2, and ListBox3) and I want to write a function to populate them with array data, like this:
Call populate_listbox(ListBox2, designAreaArray)
Where the first argument is the listbox name, the 2nd is the data.
But I do not know how to send "ListBox2" correctly, or refer to it correctly within the function.
For example:
Dim controlName as string
controlName = "ListBox1"
doesn't work, even if I define the function as follows:
Sub populate_listbox(LB As ListBox, dataArray As Variant)
Dim i As Integer: i = 0
For i = LBound(dataArray, 2) + 1 To UBound(dataArray, 2) ' Skip header row
LB.AddItem (dataArray(index, i))
Next i
End Sub
Clearly it results in a mis-matched data type error. I've tried defining "controlName" as a ListBox, but that didn't work either...
Though perhaps it is my reference to the listBox that is incorrect. I've seen SO MANY ways to refer to a control object...
MSForms.ListBox.
ME.ListBox
Forms.Controls.
Worksheet.Shapes.
The list goes on an on, and nothing has worked for me.
Try this:
Dim cMyListbox As MSForms.ListBox
Set cMyListbox = Sheet1.ListBox1 '// OR Worksheets("YourSheetName").Listbox1
cMyListbox.AddItem("An option")
Also you can populate a listbox without having to loop through the array, try this:
Dim cMyListbox As MSForms.ListBox
Dim vArray As Variant
Set cMyListbox = Sheet1.ListBox1
vArray = Range("A1:A6").Value
cMyListbox.List = vArray
Change the sub signature to match this:
Sub populate_listbox(LB As MSForms.ListBox, dataArray As Variant)
Now you can pass it like you were trying to originally.
NOTE: This only works if you used the "ActiveX" version of the listbox. I'm assuming you are because you are able to call ListBox1 straight from a module.
PS: The ActiveX controls are members off of the parent sheet object. So if you have the listbox1 on sheet1, you can also call it like Sheet1.ListBox1 so you don't get confused if you end up with multiple sheets with multiple listboxes. Also, you may want to change the name just to make it easier on yourself.
Dim controlName As OLEObject
Set controlName = Sheet1.OLEObjects("ListBox1")
Call populate_listbox(controlName, designAreaArray)
Sub populate_listbox(LB As OLEObject, dataArray As Variant)
Dim i As Integer: i = 0
For i = LBound(dataArray, 2) + 1 To UBound(dataArray, 2) ' Skip header row
LB.Object.AddItem (dataArray(Index, i))
Next i
End Sub
To access the state of a checkbox Active-X control on Sheet1:
Dim checkBox1 As Object
Set checkBox1 = Sheet1.OLEObjects("CheckBox1").Object
MsgBox checkBox1.Value