word vba macro - dynamically reference form elements - vba

I have a userform that I have created that contains a number of TextBoxes. Each of these fields are named name1, name2, name3, etc.
This userform is called from a Word macro used to process data. Depending on the data in the file the macro is run on, I want content from an array to be displayed in each of those TextBoxes.
What I want to be able to do is dynamically reference the form TextBoxes so that I can populate them from within a loop, as opposed to having to do a separate if statement for each individual value/TextBox.
So for example, instead of having to do something like this:
For p = 1 To count
if p=1 then
dForm.name1.Text=myVar(p)
end if
if p=2 then
dForm.name2.Text=myVar(p)
end if
.
.
.
etc.
Next p
I want to be able to something much more simple and efficient like:
For p = 1 To count
tempString = "name" & p
dForm.tempString.Text = myVar(p)
Next p
Unforunately though, I cannot figure out how to do this.
Is this possible? I had been hoping that something similar to what can be done in Actionscript would work, but it didn't (in Actionscript I would simply just do dForm["name"+p].Text = myVar[p]).
Any ideas/suggestions? Nothing I've tried has worked, and I haven't been able to find anything online about this. I'm sure there has got to be some work around to avoid having to do an incredible number of if statements (the 'name' TextBoxes' is just one of many repeating TextBoxes that is part of my form, so having to do if statements for all of them would take forever) ...

UserForm1.Controls("name" & p).Value = myVar(p)
or...
Private Sub FillTextBoxes()
Dim ctl As Control
For Each ctl In UserForm1.Controls
If TypeName(ctl) = "TextBox" Then
If ctl.Name Like "name*" Then
ctl.Value = myVar(p)
End If
End If
Next
End Sub

Related

Update NumericUpDown.Maximum from corresponding txt.text control using for each loop

As newbie in VBA.net i want to solve following.
I have a form with 38 text controls in 4 groupboxes. These get filled by clicking a row in a datagridview. I have 38 corresponding NUD's where i want the maximum to be equal to its corresponding text.
A 'pair' is always in one of the 4 groupboxes. Besides that there are also textboxes on the form itself to control the DGV.
I have a naming convention that makes it possible to match them easily . NUDGeel corresponds with txtGeel , NUDRood with TxtRood, NUDGroen with txtGroen etc et
Now that update is easily done if you do them one by one
NUDGeel.maximum = txtGeel.text
NUDRood.maximum = txtRood.text
etc
What i want to achieve is that this gets done in a for each loop. (Order to prevent me typing this 38 times (and also just to 'understand' it)
I can figure out how to start the loop
Dim c As Control
For Each c In Me.Controls
If TypeName(c) = "NumericUpDown" Then
'do some magic
End If
Next
I have tried to search for the magic code, and i guess from research it is pretty simple, i just donot get it .
Anyone with an idea how to fix ?
For Each tb In Controls.OfType(Of TextBox)
Dim nud = DirectCast(Controls(tb.Name.Replace("txt", "NUD")), NumericUpDown)
If nud IsNot Nothing Then
nud.Maximum = CDec(tb.Text)
End If
Next
You don't need the If statement if there are no other TextBoxes on the form besides those with corresponding NumericUpDowns.
Private Sub SetMaximum()
Dim controlNames() As String = {
"Geel", "Rood", "Name1", "Name2", ' list all the names of your controls here excluding NUD/txt
}
For Each controlName As String In controlNames
CType(Me.Controls("NUD" & controlName),NumericUpDown).Maximum = CType(Me.Controls("txt" & controlName),TextBox).Text
Next
End Sub
This assumes that all the controls are not inside any containers. If they are, you must use the container (panel, groupbox, etc) instead of Me.

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!

Is there a way in VBA to iterate through specific objects on a form?

I would like to have a subroutine in VBA that conditionally changes the Enabled property of each of 20+ buttons on a form via iteration rather than code them all by hand. These buttons are named similar to tables that they process. For example: A table to process is called "CUTLIST"; its corresponding button is called "but_CUTLIST". There is another table that holds the list of tables to be processed (used for iteration purposes in other subs).
What I have so far...
Private Sub txt_DataSet_GotFocus()
Dim sqlQry as String
Dim butName As String
Dim tableList As Recordset
Dim tempTable As Recordset
Set tableList = CurrentDb.OpenRecordset("TableList") 'names of tables for user to process
tableList.MoveFirst 'this line was corrected by moving out of the loop
Do Until tableList.EOF
sqlQry = 'SQL query that determines need for the button to be enabled/disabled
Set tempTable = CurrentDb.OpenRecordset(sqlQry)
If tempTable.RecordCount > 0 Then
'begin code that eludes me
butName = "but_" & tableList!tName
Me(butName).Enabled False
'end code that eludes me
End If
tableList.MoveNext
Loop
End Sub
If I remember correctly, JavaScript is capable of calling upon objects through a variable by handling them as elements of the document's object "array." Example: this[objID]=objVal Is such a thing possible with VBA or am I just going about this all wrong?
Viewing other questions... is this what's called "reflection"? If so, then this can't be done in VBA. :(
In case more explanation helps to answer the question better... I have a utility that runs SQL queries against a pre-defined set of tables. Each table has its own button, so that the user may process a query against any of the tables as needed. Depending on circumstances happening to data beforehand, any combination of the tables may need to be queried via pressing of said buttons. Constantly referring to the log, to see what was already done, gets cumbersome after processing several data sets. So, I'd like to have the buttons individually disable themselves if they are not needed for the currently focused data set. I have another idea on how to make that happen, but making this code work would be faster and I would learn something.
I'm not an expert on VBA, but I would re-arrange the code to take advantage of the fact that you can iterate through the control collection in the user form
Something like this:
Dim ctrl as Control
For Each ctrl in UserForm1.Controls
If TypeName(ctrl) = "Button" Then
ctrl.Enabled = True
End If
Next
You can pass the button name to some other function (from this loop) to determine whether the button in question should be enabled / disabled.

Dynamically looping through picture box controls in Visual Basic 2010 does not seem to follow any order

In a Visual Basic 2010 form application I have the below code snippet:
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
ctlControl.image = Nothing
End If
Next ctlControl
My problem is when it loops through the controls it does not start with the top left control and it seems to go over each picture box in random order.
How do I control the order of which picture box is updated next. Is there a property similar to tab index(in VB 6) which I can manipulate to control the order in which picture boxes are updated by my loop?
As a more proper and sure way, I would get each picture box, keep handles and locations of them, then sort them according to their location. Now they are ready to use. Here is an example:
Public Class Form1
Structure Pbox
Dim handle As IntPtr
Dim top As Integer
Dim left As Integer
End Structure
Dim pboxlist As New List(Of Pbox)
Sub ClearImages()
pboxlist.Clear()
For Each c As Control In Me.Controls
If TypeName(c) = "PictureBox" Then
Dim x As New Pbox
x.top = c.Top
x.left = c.Left
x.handle = c.Handle
End If
Next
pboxlist.OrderByDescending(Function(a) a.top).ThenByDescending(Function(a) a.left)
For Each item In pboxlist
Dim x As PictureBox = PictureBox.FromHandle(item.handle)
x.Image = Nothing
Next
End Sub
End Class
Another approach is using a good naming, so that you can use their names to sort them. For instance, PictureBox1 will come before PictureBox2 if you sort. So you should use PictureBox1 for the very top and left one and PictureBox2 for the next one and so on...
EDIT: Using Tag property, as John Bustos suggested, instead of names is an easier and better idea. So without getting lost in names, you can sort picture boxes according to their Tags which are defined by you.
As some of the other guys have said you could use the TAG property which is probably the BEST shot, whenyou are dynamically creating the picture boxes use a counter and add the counter value to the TAG property. if you added the picture boxes manually then simply start with the top left and work towards the right and add a value in the TAG property field of each one starting with 1 and increasing by one each time and continue until the row is completed then carry on with the next row.
Finally when your ready to loop through the picture boxes simply follow the pattern below..
'Calc number of picture boxes
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
Counter = Counter + 1
End If
Next ctlControl
ThisBox = 1
Do
For Each ctlControl In Me.Panel1.Controls
If TypeName(ctlControl) = "PictureBox" Then
If CInt(ctlControl.Tag) = ThisBox Then
CLEAR-YOUR-IMAGE-HERE
ThisBox = ThisBox + 1
End If
End If
Next ctlControl
Loop Until ThisBox = Counter
Note:
Its IMPORTANT your numbers that you place in the TAG property are consecutive or you will become forver stuck in the DO-LOOP!!!
The order of the controls was determined by the order they were added to the panel and not tabstop index. You can change that by carefully reorganizing the order they were added to the panel in the form's designer file, though I'd not recommend it.
The PictureBox control has a Text property you can use instead of Tag.
It doesn't come up in Intellisense because it's an infrastructure property, but it's there.
http://msdn.microsoft.com/en-us/library/hc9k45f4(v=vs.110).aspx
(I wanted to comment on Zaf Khan's answer but I don't have the rep, yet.)

Identify and populate a listbox

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 ;