Identify and populate a listbox - vba

It's a riddle for me: what is the syntax to populate a listbox? But first: how do you identify a listbox? In many forums I read: ListBox1.Additem... But how do they know it's 'ListBox1'?

That's the default name for a ListBox control when you add it to your form. VB and VBA automatically name new or unnamed controls with the name of the type of control, suffixed with an integer identifier.
It's completely irrelevant what your control is called. The point of the sample code is to demonstrate a concept. You should replace ListBox1 with whatever your control is named.
And you should definitely name your controls something other than the default, because as you observe here, it's not very descriptive.
It used to be recommended by everyone to name controls following some type of quasi-Hungarian notation with a 3-letter prefix indicating the type of control, followed by your regular descriptive name. Over the past few years, there's been a big backlash against anything that looks like Hungarian notation, but I think it's still quite useful with regards to naming GUI controls, so I still use it. For a ListBox control, I might call it something like: lstCustomers or lstItemsForSale. But it's completely optional: again, what you choose to name your controls is irrelevant to how the code works or how the application will behave.
So, to populate a listbox in VB 6 and/or VBA, you'd use the following code, where myListBox is the name of your ListBox control:
' Add an item to the end of the list
myListBox.AddItem "Peaches"
' Insert an item at the top of the list
myListBox.AddItem "Apples", 0
' Remove the first item in the list
myListBox.RemoveItem 0

ListBox1.AddItem is for loading a single column ListBox (CodyGrey's answer covers that).
If you are using a multi column ListBox (property .ColumnCount > 1), then you need to load an Array into .List. The following snippet loads 3 rows into a 2 column ListBox
Dim dat(0 To 2, 0 To 1) As Variant
dat(0, 0) = "A"
dat(0, 1) = "B"
dat(1, 0) = "C"
dat(1, 1) = "D"
dat(2, 0) = "E"
dat(2, 1) = "F"
ListBox1.List = dat
Accessing the List Box: (this will vary for different versions of Word, this is for 2003)
To access the ListBox properties:
Display the "Control Toolbox" toolbar
Go To Design Mode
Click the control, and select Properties on the "Control Toolbox"
The Name property should now be visible and editable
Other properties, such as ColumnCount etc can also be set

When using VBA in Access, a good way to fill in the whole listbox at once is to use the RowSource property when the ListBox is a ValueList. The AddItem method is a bit slow when adding many entries.
For example, to fill in a list of files from a directory, you could use:
Dim src As String
Dim S as String
S = Dir(FullPathToCurrentDirectory & "\*.*")
While Len(S) <> 0
src = src & """" & S & """;"
S = Dir
Wend
If Len(src) = 0 Then src = """"";"
Me.lstFiles.RowSource = Left(src, Len(src) - 1) ' leave off the last ;

Related

Convert String to Textbox Name

I have a 9 Textboxes and I want to get their values with Val(Me.TxtBoxName.Text)).
The Textboxes already all exists and are named. I want to build the names and access their values with
For i = 0 To 2
For j = 0 To 2
getText = "Me." & "eing_" & i & j & ".Text"
Debug.WriteLine(Val(getText))
Next
Next
(This will get me the Textbox Names eing_00, eing_01, eing_02, eing_10, eing_11, eing_12, eing_20, eing_21, eing_22)
But this does not work as getText is a String. How can I convert it so I can access the Textbox properly?
That's not how it works. You don't magically turn a String into an identifier.
Fortunately though, all controls have a Name property and you can use that property to index the Controls collection of the parent to get a reference to a control. When you add a control in the designer, the Name property is the same as the field declared to refer to the control, e.g. if you add a Button to a form and do this:
MessageBox.Show(Button1.Name)
then it will display "Button1".
So, in your case, that means that you would replace this nonsense:
getText = "Me." & "eing_" & i & j & ".Text"
with this:
getText = Controls($"eing_{i}{j}").Text
That assumes that the controls were added directly to the form. If they were added to some other container, e.g. a Panel, then you must use the Controls collection of that container, rather than that of the form.
I think you need to pay attention to the difference between numbers and strings. The Name property of a Control is a String but the control itself is type Control and the inherited type TextBox in this case. We can refer to an item in the ControlsCollection by providing a String which is the Name property of the control.
The .TryParse takes a String and checks if the string can be converted to a the Type you are parsing. In this case Double. It returns True or False and if True it assigns the converted value to the second parameter. It is important to use this because users can enter any old thing in a text box.
I wasn't sure what kind of number you were expecting so I chose Double. Change to a narrower type if you can. If you need to manipulate numbers with arithmetic they cannot be strings (the Text property is a string)
As a side note, you can do several things with a list of numbers, Average, Max, Min, or Sum.
Dim total = numbers.Sum
Turn on Option Strict to help identify type problems.
Private Sub OpCode()
Dim numbers As New List(Of Double)
For i = 0 To 2
For j = 0 To 2
Dim getText = "eing_" & i & j
Dim ctrl = Controls(getText)
Dim d As Double
If Double.TryParse(ctrl.Text, d) Then
numbers.Add(d)
End If
Next
Next
For Each d In numbers
Debug.Print(d.ToString)
Next
End Sub

Saving Custom Document Properties in a Loop

I'm trying to save the values of data that have been input into my form. There are a total of about 50 different fields to save across 5 different agents, so I loaded the data into arrays.
I've tried saving the fields in a loop, but it doesn't seem to work in a loop, only if each field has a separate line, which is a lot of code and messy. The Ag1Name, Ag2Name and Ag3Name are the names of my textboxes that the user enters to populate the form.
Sub LoadAndSaveData()
NumberofAgents = 3
Dim AgentName(3) as String
AgentName(1) = Ag1Name.Value
AgentName(2) = Ag2Name.Value
AgentName(3) = Ag3Name.Value
For Count = 1 To NumberOfAgents
With ActiveDocument.CustomDocumentProperties
.Add Name:="AgentName" & Count, LinkToContent:=False, Value:=AgentName(Count), Type:=msoPropertyTypeString
End With
Next Count
End Sub
The data doesn't get saved to the Custom Document Properties when the code is set up in a loop like the above. Since there are so many values to save and all the data is already in arrays, I would much prefer to use a loop rather than write out a separate line of code for all ~50 of the values. It does seem to work when each field is saved in a separate line of code.
I think this would probably get what you want. You don't really need to count the document properties first, only increment with the ones you want to update. Hopefully the only document properties you want contain the name AgentName in it.
ReDim AgentName(0) As String
Dim P As Long
For Each c In ThisDocument.CustomDocumentProperties
If InStr(1, c.Name, "AgentName", vbTextCompare) > 0 Then
ReDim Preserve AgentName(P)
AgentName(P) = c.Value
P = P + 1
End If
Next c
As a guest I cannot post a comment here, but the code you gave works OK here.
However, there is a problem with creating legacy custom document properties programmatically, because doing that does not mark the document as "changed". When you close the document, Word does not necessarily save it and you lose the Properties and their values.
However, if you actually open up the Custom Document Property dialog, Word does then mark the document as "changed" and the Properties are saved.
So it is possible that the difference between your two scenarios is not the code, but that in one scenario you have actually opened the dialog box to check the values before closing the document and in the other you have not.
If that is the case, here, I was able to change this behaviour by adding the line
ActiveDocument.Saved = False
after setting the property values.
If you do not actually need the values to be Document Properties, it might be better either to use Document Variables, which are slightly easier to use since you can add them and modify them with exactly the same code, or perhaps by storing them in A Custom XML Part, which is harder work but can be useful if you need to extract the values somewhere where Word is not available.
You can make this even easier by looping the controls on the UserForm, testing whether the control name contains "Ag" and, if it does, create the Custom Document Property with the control's value - all in one step.
For example, the following code sample loops the controls in the UserForm. It tests whether the controls Name starts with "Ag". If it does, the CustomDocumentProperty is added with that control's value.
Sub LoadAndSaveData()
Dim ctl As MSForms.control
Dim controlName As String
For Each ctl In Me.Controls
controlName = ctl.Name
If Left(controlName, 2) = "Ag" Then
With ActiveDocument.CustomDocumentProperties
.Add Name:=controlName, LinkToContent:=False, value:=ctl.value, Type:=msoPropertyTypeString
End With
End If
Next
End Sub
I feel a little stupid... I just realized that the reason that the code wasn't working was that the variable NumberofAgents was not being calculated correctly elsewhere in my code. I've got it working now. Thanks for your thoughts!

Reading checkbox name from text file

I'm trying to read a checkbox name from a txt file. I'm not sure if this is posible but i tried this:
Me.(My.Computer.FileSystem.ReadAllText(Application.StartupPath & "\Settings\language.txt")).Checked = 1
Sadly it didnt work, i get the error "identifier expected" and it underlines the dot after the Me.
The basic problem here is understanding what things are resolved at compile time vs what things are resolved at run time.
A checkbox name is an identifier that must resolve at compile time. You see a nice name like CheckBox1 in your source code, but when the program actually runs all you really have is a reference consisting mainly of a number representing an offset into your program's memory address space. The CheckBox1 name as a variable no longer exists; only the object reference remains.
On the other hand, StartupPath and the contents of the text file are only strings. They are not identifiers, and their values are not known until much later, when the program is already running.
The good news is, in the case of WinForms controls the variable name is preserved as data in the Control object, and you can search for it. You just need to use a method that will look at your Control objects, like this:
Dim language As String = My.Computer.FileSystem.ReadAllText(Path.Combine(Application.StartupPath, "\Settings\language.txt"))
Dim languageBox as CheckBox = DirectCast(Me.Controls(language), CheckBox)
Or maybe this:
Dim languageBox as CheckBox = Me.Controls.OfType(Of CheckBox)().FirstOrDefault(Function(box) box.Name = language)
Or maybe your checkbox is nested within a GroupBox or Panel. In that case, you need to change Me for the name of the GroupBox, Panel, or other container control that directly holds the checkbox.
what is the name of checkbox in form ?...
Dim _chkbxName$ = My.Computer.FileSystem.ReadAllText(Application.StartupPath & "\Settings\language.txt")
For Each cnt As Control In Me.Controls
If TypeOf cnt Is CheckBox Then
If cnt.Name = _chkbxName Then
CType(cnt, CheckBox).Checked = True
Exit For
End If
End If
Next

Protect custom properties in word

We have several templates in Word2016 which uses custom variables, these variables should be updated with my macro, so that data gets pushed to the Database when the user changes them.
Unfortunately we have some users that deletes the variables in the documents(in the text not in the file properties) which means that the database does not get updated. Is there a setting to make custom properties not able to be deleted from the text?
Properties for one example document are listed below
This is how it should look like in the document
Then this is what happens sometimes, which should not be allowed
By doing so I do not need to loop through document to find variables, because I can simply loop through custom properties:
Public Sub initializeVariablesFromDB(doc As Document, dokID As String)
Dim docProp As Object
Dim rowNumber As Integer
'Get valid properties
If CPearson.IsArrayEmpty(Settings.validPropertiesArray) Then
Settings.validPropertiesArray = Post.helpRequest("xxxxxx?Dok2=1")
End If
'We create the docid just in case....
Call CustomProperties.createCustomDocumentProperty(doc, "_DocumentID", dokID)
'We loop through all custom properties
For Each docProp In doc.CustomDocumentProperties
rowNumber = CPearson.findRownumber(docProp.name, Settings.validPropertiesArray)
If rowNumber <> -1 Then
'we clear all SIGN properties...
If InStr(UCase(docProp.name), "SIGN") > 0 Then
docProp.value = ""
End If
'We check if we should use value from DB
If Settings.validPropertiesArray(rowNumber, 1) = "0" Or InStr(UCase(docProp.name), "DOCUMENTCREATEDDATE") > 0 _
Or InStr(UCase(docProp.name), "DOCUMENTCREATOR") > 0 Or InStr(UCase(docProp.name), "DOCUMENTNAME") > 0 Then
'Update from DB
Call Post.updateDBFromCustomProperties(docId:=dokID, PropertyName:=docProp.name, whoRules:="dBrules", doc:=doc)
End If
End If
Next docProp
End Sub
This will fail for the _DocumentSubject in the text, the new value to the property will be collected but is not reflected in the text anymore because the user deleted the variable in the text, can I prevent this?
You can put the field into a Content Control and lock the content control. Users who know enough about MS Word can still delete the content control but they would need to take some fairly deliberate steps to do so (and I'm guessing those people are not the problem).
Normal fields, including document properties, can be added to content controls (probably best to use rich text).
You can put the field into a Content Control, seems like you can not do that :( – skatun 10 hours ago
You can totally do it, follow these steps (Word 2010):
Add a custom property to a new blank (even unsaved) document _SlowLearner
Add a RichText content control from the DeveloperTab
Click inside the content control
Insert > Quick Parts > Field
Filter fields Document Information > select DocProperty
Select property: _SlowLearner > OK
Note: the field is protected when the Content Control is fully locked. This has the curious side effect that the Content Control does NOT allow the field to update normally... to get around this you need to add some more code to your VBA, like this:
Sub UpdateProtectedField()
With ActiveDocument.ContentControls(1)
.LockContents = False
.Range.Fields(1).Update
.LockContents = True
End With
End Sub
One possibility is to use continuous sections breaks and protect sections, not an ideal solution because then images can not be formatted and so on..

How can I change a toolbox item property by referring to the item through a variable?

Sorry for the confusing title - here is the code I'm using.
Example code -
If bolCorrect = False Then
intIncorrect += 1
temp3 = "picture" + CStr(intIncorrect)
temp3.Visible = True
I've got several images all, with names of picture[number-from-0-to-10], and I want them to show depending on the count of a variable.
The error it throws up is that 'Visible' is not part of 'String'. How can I get the interpreter to look at 'temp3' in this instance, and refer to the toolbox item rather than the type of the variable (e.g. string)?
You need to refer to the actual name property you have set for the picturebox control (if you are using the picturebox control)
So if your picture box control is named pb1
pb1.Image = System.Drawing.Image.FromFile("picture" + counter + ".jpg")
pb1.Visible = True
You should generally try to avoid addressing controls via strings, that’s usually just a hack around a proper solution. Instead, maintain a variable to that control, or, in your case, maintain an array of the relevant controls and access them via an index.
That said, it is possible to get a control given its name via the Form.Controls collection:
Dim ctl = Me.Controls("picture" + CStr(intIncorrect))