A simple question for which I didn't find any answer yet:
I have a Word template document with several UserForms.
Now on AutoNew() a new document based on this template should first read the values from the hidden userforms (the current values are stored somewhere in the template) into global variables for further processing.
Now how can vba read the values in the userforms from the document without usually unnecessary to show the userforms ? These userforms are only there to change values if [rarely] desired and should not be automatically displayed (they are opened through controls which later disappear as soon as everything is OK).
My few trials all led to errors:
Sub Fertigstellen()
Load Empfängerdaten ' proposition from Raymond Wu
Dim X
' X = ActiveDocument.Forms("Empfängerdaten.QL4").Text ' error 438
' X = ActiveDocument.Forms("Empfängerdaten").QL4.Text ' error 438
' X = ActiveDocument.QL4.Text ' error 438
' X = ActiveDocument.Empfängerdaten.QL4.Text ' error 438
' X = ActiveDocument.Content("QL4").Text ' compiling error
' X = ActiveDocument.Empfängerdaten.QL4.Text ' error 438
' X = ActiveDocument.UserControl("QL4").Text ' compiling error
If X Like "CH" Then ' alle Daten sind vorhanden
Stop
End If
QR_Dialog.Show
End Sub
What I need is which command to use (if there is any).
All methods I could find are only able to read values from textboxes enbedded in the document itself. The documents's code doesn't find the userforms' textboxes.
However, I need to use userforms which are hidden when printing the document.
The values from the userforms are only used for generating a QR code, they should not appear anywhere else. Note that MS Word doesn't appear to have an Activedocument.Forms![MyField].Value property to access userforms (unlike Access or Excel).
NB: the reverse is no problem: from the userforms it's very easy to fill the global variables on opening or closing them.
The answer is quite as simple as my question:
I found it in this Excel blog.
For me highly surprising (as I'm mainly involved with Access) is that Word doesn't need a "userform" to be specified - its name is sufficient !
The following code now just needs to be adapted to the different QL# textboxes, the contents of which need to be checked case by case:
Sub Fertigstellen()
Dim fTextBox As Object
For Each fTextBox In Empfängerdaten.Controls
If TypeName(fTextBox) = "TextBox" Then
Select Case fTextBox.Name
Case "QL4"
If fTextBox.Text Like "ABC" Then
' OK
Else
Debug.Print fTextBox.Name & ": " & fTextBox.Text
End If
' … … …
Case Else
Debug.Print fTextBox.Name & " should be implemented"
End Select
End If
Next
' … … …
End Sub
On this basis it should probably also be possible to access only one single textbox, but I don't need to as all userform's values must be verified.
Thanks for the hints anyway.
Related
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!
I need to save data that was input into a Microsoft Word form in VBA after the macro terminates. I have a form that has about 40 fields with names, addresses, phone numbers etc and I don't want the user to have to input everything again in case they realize that they made a mistake and need to change 1 item. Currently, the macro deletes all the data that was input into the form when the form closes.
I've looked around on forums and Google but haven't had any luck.
This code brings up my input form from a word document with a command button:
Private Sub CommandButton1_Click()
InputForm.Show vbModeless
End Sub
This code closes the form:
Private Sub CloseForm_Click()
Dim Closing As Integer
Closing = MsgBox("This will exit the form and erase all the data that has been input. You may want to review the documents to ensure they were generated correctly before closing the form. Click Yes to proceed to close the form or No to go back to the form.", vbYesNo + vbQuestion, "Exit Confirmation")
If Closing = vbYes Then
Unload InputForm 'This closes the Input Form and returns the user to Word
End If
End Sub
The macro does not save the information input into the form. Whenever it closes, all the information is erased; I want it saved.
Options for saving data in the document
Document.Variables This has been available in Word since the very early days and is available in all versions since Word 2.0 (early 1990s). It's simply a place to store any number of strings, associated with an identifier (name). In essence, a key-value pair. Important to note: It must be a pair, there cannot be a key without a value and a value cannot be a zero-length string. Assigning a value to a name creates the Variable; deleting the value removes the Variable. These are not visible to the user.
Example:
Document.Variables("Name").Value = "John Doe"
Document.CustomDocumentProperties Similar to Variables but data-types other than strings are available. There is a limit to the length of the data that can be stored. They are visible to the user through the "Properties" interface. They must be explicitly created / destroyed. This was introduced in, I believe, Word 97.
Example:
Document.CustomDocumentProperties.Add(Name As String, LinkToContent As Boolean, _
[Type], [Value], [LinkSource])
CustomXMLParts These are XML "pages" saved in the docx zip package. This is a good way to save data, and it's easily accessible from the closed file. The capability was introduced in Word 2010, so won't be available in older versions nor in doc file formats, only docx.
The coding is more complex than for Variables or Properties.
The usual way to work with this in the described scenario is to create the CustomXMLPart in the document (or template - documents created from the template should carry over the CXP). Then all the code needs to do is read/write from the xml nodes already available. If the VBA coder has no background in XML parsing there will be a steep learning curve.
There are many ways to go about reading and writing, here's a snippet that demonstrates the basics of reading, to give an idea of what's involved:
Sub ReadCXP()
Dim cxp As Office.CustomXMLPart, CXPs As Office.CustomXMLParts
Dim doc As Word.Document
Dim sUri As String
Set doc = ActiveDocument
'object, not collection, because ID is unique and can return only one
'ID is unique to each CustomXMLPart - this is won't work as it stands!
Set cxp = doc.CustomXMLParts.SelectByID("{32B20BB8-F4FB-47D6-9DF4-FFAF5A1D8C18}")
Dim xmlNds As Office.CustomXMLNodes
Dim xmlNd As Office.CustomXMLNode
Set xmlNds = cxp.SelectSingleNode("trees").ChildNodes
For Each xmlNd In xmlNds
Debug.Print xmlNd.XML
Next
End Sub
I created my own Outlook form to use it as standard surface to enter certain orders instead of the normal message form. The creation, editing and sending works perfectly fine and in the next step I want to insert some code via VBA.
My problem is that I can´t access the objects of my form in the VBA editor. E.g. I want to show a message box when a certain checkbox is checked. According code would be:
Sub example()
If CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
When I run the code I get the error that the object could not be found. The same goes for every other object, like textboxes or labels etc.
I guess the solution is pretty simple, like putting Item. or sth. like that in front of each object. But so far I wasn't able to find the solution.
I´m using Outlook 2010.
I know this is a year too late but you'll want to do something like this example below. It's kinda a work around but you can get whatever value was selected.
Sub ComboBox1_Click()
Set objPage = Item.GetInspector.ModifiedFormPages("Message")
Set Control = objPage.Controls("ComboBox1")
MsgBox "The value in the " & Control.Name & _
"control has changed to " & Control.Value & "."
End Sub
You should be able to get the value, just get a handle on the object you want using the Inspector
The following is an excerpt from here
When you use a custom form, Outlook only supports the Click event for
controls. This is a natural choice for buttons but not optimal for
controls like the combo box. You write the code by inserting it into a
form’s VBScript editor. You need to have the Outlook form open in the
Form Designer and click the View Code button found in the Form group
of the Developer tab.
Sub CheckBox1_Click()
msgbox "Hello World"
End Sub
The code page is fairly minimal with no syntax highlighting. I just tried this now and it does work. Dont forget to Publish your form to pick up the new changes.
I know this is almost 6 years late but, in VB and VBA, simply start with the form name. (And if that doesn't work, just keep going up a parent object and you'll get there.) So, your code becomes:
Sub example()
If MYFORMNAME.CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
Of course, after typing "MYFORMNAME." you'll know if it will work because typomatic will kick in when the system recognizes "MYFORMNAME" after you hit the period.
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
I want to make a special list of figures with use of VBA and here I am using the function
myFigures = ActiveDocument.GetCrossReferenceItems(Referencetype:="Figure")
In my word document there are 20 figures, but myFigures only contains the first 10 figures (see my code below.).
I search the internet and found that others had the same problem, but I have not found any solutions.
My word is 2003 version
Please help me ....
Sub List()
Dim i As Long
Dim LowerValFig, UpperValFig As Integer
Dim myTables, myFigures as Variant
If ActiveDocument.Bookmarks.Count >= 1 Then
myFigures = ActiveDocument.GetCrossReferenceItems(Referencetype:="Figure")
' Test size...
LowerValFig = LBound(myFigures) 'Get the lower boundry number.
UpperValFig = UBound(myFigures) 'Get the upper boundry number
' Do something ....
For i = LBound(myFigures) To UBound(myFigures) ‘ should be 1…20, but is onlu 1…10
'Do something ....
Next i
End If
MsgBox ("Done ....")
End Sub*
Definitely something flaky with that. If I run the following code on a document that contains 32 Figure captions, the message boxes both display 32. However, if I uncomment the For Next loop, they only display 12 and the iteration ceases after the 12th item.
Dim i As Long
Dim myFigures As Variant
myFigures = ActiveDocument.GetCrossReferenceItems("Figure")
MsgBox myFigures(UBound(myFigures))
MsgBox UBound(myFigures)
'For i = 1 To UBound(myFigures)
' MsgBox myFigures(i)
'Next i
I had the same problem with my custom cross-refference dialog and solved it by invoking the dialog after each command ActiveDocument.GetCrossReferenceItems(YourCaptionName).
So you type:
varRefItemsFigure1 = ActiveDocument.GetCrossReferenceItems(g_strCaptionLabelFigure1)
For k = 1 To UBound(varRefItemsFigure1)
frmBwtRefDialog.ListBoxFigures.AddItem varRefItemsFigure1(k)
Next
and then:
frmBwtRefDialog.Show vbModeless
Thus the dialog invoked several times instead of one, but it works fast and don't do any trouble. I used this for one year and didn't see any errors.
Enjoy!
Frankly I feel bad about calling this an "answer", but here's what I did in the same situation. It would appear that entering the debugger and stepping through the GetCrossReferenceItems always returns the correct value. Inspired by this I tried various ways of giving control back to Word (DoEvents; running next segment using Application.OnTime) but to no avail. Eventually the only thing I found that worked was to invoke the debugger between assignments, so I have:
availRefs =
ActiveDocument.GetCrossReferenceItems(wdRefTypeNumberedItem):Stop
availTables =
ActiveDocument.GetCrossReferenceItems(wdCaptionTable):Stop
availFigures = ActiveDocument.GetCrossReferenceItems(wdCaptionFigure)
It's not pretty but, as I'm the only person who'll be running this, it kind of works for my purposes.