VBA and Publisher - replacing text in a master page - vba

Recently I have been struggling with publisher at work, and the most recent source of concern is the software inability to give something a simple as an auto-updating "total pages" count.
I decided to look into macros for a solution, but with my limited knowledge of VBA I only managed to get so fat before hitting a roadblock.
I want to create a macro that automatically starts when the document is opened and as frequently as possible updates a textbox in the master page.
The textbox should show "page X of Y", but due to publisher limitations only the "X" is automatically updated without using a macro.
What I have right now:
Private WithEvents PubApp As Publisher.Application
Private Sub Document_Open()
Set PubApp = Publisher.Application
End Sub
Private Sub PubApp_WindowPageChange(ByVal Vw As View)
MsgBox "Your publication contains " & _
ActiveDocument.Pages.Count & " page(s)."
End Sub
The macro automatically starts when the document is opened and creates a popup with the total page number every time the user changes page.
So, I accomplished the first half of my goal, but I need some help with the rest.

Here's a working macro for anyone with the same problems
Private WithEvents PubApp As Publisher.Application
Private Sub Document_Open()
Set PubApp = Publisher.Application
End Sub
Private Sub PubApp_WindowPageChange(ByVal Vw As View)
Dim mp As MasterPages
Set mp = ActiveDocument.MasterPages
With mp.Item(1)
.Shapes(19).TextFrame.TextRange.Text = ""
.Shapes(19).TextFrame.TextRange.InsertPageNumber
.Shapes(19).TextFrame.TextRange.InsertBefore _
NewText:="Page "
If ActiveDocument.Pages.Count > 9 Then
.Shapes(19).TextFrame.TextRange.InsertAfter _
NewText:=" of " & _
ActiveDocument.Pages.Count
Else
.Shapes(19).TextFrame.TextRange.InsertAfter _
NewText:=" of 0" & _
ActiveDocument.Pages.Count
End If
End With
End Sub
X In mp.Item(X) identifies your master page (in case you have more than one).
Y in Shapes(Y) identifies the target text box.
The macro runs in background without the need of active input and refreshes the content of the target text box on every page change.
Since the automatic page numbering format used in the document is "01, 02, 03 ... 11, 12 13" I included an If selection to add a 0 before the total page count if said number is lower than 10.
I'm sure there are more elegant ways of solving this problems, but hey, at least it works.

Here is a clarification for anyone else trying to research the limited information on programmatically changing the header or footer in the master pages using VBA in Publisher. This tip also includes a clarification of how to Add Text To the Left,Center,Right portions of the header:
All the credit goes to M.Baroni
Function FixHeader1()
Dim mp As MasterPages
Set mp = ActiveDocument.MasterPages
With mp.Item(1)
.Header.TextRange.text = "My Publication" & vbTab
.Header.TextRange.InsertPageNumber
.Header.TextRange.InsertAfter NewText:=vbTab & MonthName(Month(Date)) & " " & Year(Date)
End With
End Function

Related

Multiple text boxes to call the same VBA procedure

I am developing an Access Database and have several forms; all of which have the same text box on them.
The text box for each form is from the same record source, same name, same properties, etc. After the textbox is updated I have VBA running an Instr procedure which captures key phrases commonly used in these text boxes and replaces them with a common phrase.
How can I get each text box from each form to call the same procedure, that way if I have to improve the code over time I am only doing so in one place versus going to each form to update the code.
Example code.
textbox1_AfterUpdate()
Dim A as TextBox
Set A= Me.Textbox1
If InStr(A," Attachment Number ") Then
Me.FunctionalArea.SetFocus
A=Replace(A,"Attachment Number","<<Att."&" "& Left(Me.FunctionalArea).text,1)&""&"XXX>>")
A=SetFocus
End If
If InStr(A, " Program Name ") Then
A = Replace(A, " Program Name ", " <<ProgramNameXX>> ")
End If
If InStr(A, " Office Address ") Then
A = Replace(A, " Office Address ", " <<OfficeAddressXX>> ")
End If
You just call the code with a parameter of the textbox.
Something along the lines of
Public Sub textbox1_AfterUpdate()
DoTextBoxActions Me.Textbox1
End Sub
Public Sub DoTextBoxActions(ByRef ipTextBox As TextBox)
If InStr(ipTextBox.Text, " Attachment Number ") Then
ipTextBox.FunctionalArea.SetFocus
ipTextbox=Replace(ipTextbox.Text,"Attachment Number","<<Att."&" "& Left(ipTextbox.FunctionalArea).text,1)&""&"XXX>>")
ipTextBox.Parent.SetFocus = SetFocus
End If
If InStr(ipTextBox.Text, " Program Name ") Then
ipTextBox = Replace(ipTextBox.Text, " Program Name ", " <<ProgramNameXX>> ")
End If
If InStr(ipTextBox.Text, " Office Address ") Then
ipTextBox = Replace(ipTextBox, " Office Address ", " <<OfficeAddressXX>> ")
End If
End Sub
You can do this.
When you place that text box on each form, in place of building a "event" code stub, you can enter this:
=MyFunctionName()
Or, thus in your case, you would place this code in a standard code module (NOT the forms code module).
Public Function MyGlobalAfterUpdate
' pick up the form and the control
' do this first, do this fast, do this right away
' since a timer event, mouse click, focus change etc. can
' case the screen.ActiveForm, and screen.ActiveControl to change
' once we grab these values, then you ok
Debug.Print "global after"
Dim MyControl As TextBox
Dim MyForm As Form
Set MyForm = Screen.ActiveForm
Set MyControl = Screen.ActiveControl
Debug.Print "Control name = " & MyControl.Name
Debug.Print "Text of control = " & MyControl.Value
Dim strText As String
strText = MyControl.Value
Debug.Print strText
' note that we have FULL use of the form values
' in place of me!Some value, or control?
' you can go
MyForm.Refresh
MyForm!LastUpdate = now()
' save the data in the form
' If MyForm.Dirty = true then MyForm.Dirty = False
End sub
So you are free to do whatever you want in this code. And you simple replace "me" the forms reference with MyForm, but once you grabbed the active form, then anything you would or could do with "Me", you can do the SAME with MyForm. As noted, you could in theory using Screen.ActiveForm, but you are MUCH better to pick up a reference as fast as possible and as soon as possible, since those values and focus could change, and often it will - so get/grab/take a reference to the controls as fast and as soon as possible. Once you grabbed the reference from screen, then minor changes in focus etc. don't matter - since you picked up the form and control right away.
The key concept, the key takeaway? you can grab both the current form with screen.ActiveForm, and you can get/grab the current control that fired the after update event with Screen.ActiveControl.
So, in summary:
Don't create a code stub in the form. In the controls after update event, place the name of the PUBLIC function in a STANDARD code module.
eg like this:
=MyGlobalAfterUpdate()

Access 2016 UserForm Called From Word Covered by Word Document

Access 2016 Popup UserForm Obscured by Word Document
I have an Access 2016 database that contains authors and references that I use in my Word Documents. The authors are contained in one table which is joined to the references table.
If the data I need to input into Word isn't in the database, I have an Access UserForm that pops up to enter the data into the database. The problem is that I work with 2 Word documents opened side by side which takes up the full screen and the Access Userform is covered behind the Word documents. Due to the UserForm being popup and modal, it freezes everything. The only workaround I have come up with is to force the Word windows to minimize when the popup is called, but this is not optimal.
This Sub in Word calls the Access UserForm:
Sub OpenDataEntryForm(stAuthor As String)
Dim acc As Access.Application
Dim lngAuthor As Long
Dim stOpenArgs As String
Application.WindowState = wdWindowStateMinimize
Set acc = New Access.Application
With acc
.Visible = False
.OpenCurrentDatabase stAccPath 'stAccPath is a constant
lngAuthor = .DLookup("[ID]", "[tblAuthors]", "[Authors] = '" & _
stAuthor & "'")
stOpenArgs = CStr(lngAuthor)
.DoCmd.OpenForm "frmDataEntry", acNormal, , , acFormAdd, _
acDialog, stOpenArgs
.Quit
End With
Application.WindowState = wdWindowStateMaximize
End Sub
This is the Load Event Sub of the Userform:
Private Sub Form_Load()
If Not IsNull(Me.OpenArgs) Then
Me!Authors = CLng(Me.OpenArgs)
Me.SetFocus
Me!OriginalRef.SetFocus
Else: Me!Authors.Locked = False
End If
End Sub
With the above code, the Word windows minimize so that data can be entered into the Access UserForm. I need to find a way to bring the Access UserForm to the front of the documents when it's called so that I don't have to minimize the document windows.
Using this link from Cindy Meister: VBA API declarations. Bring window to front , regardless of application, I was able to add this line of code to my Access UserFrom Form_Load Event:
AppActivate Me.Caption
This now brings the UserForm to the front whenever it is called.

VBA: userform multipage template

I'm making a Userform that would allow the user to changes the bounds on several charts at once. I figured out how everything should look on the first multipage, and now I'm wondering if there's any way I could add 11 pages at once to look just like the first page. My userform appears below.
I am still in the design phase for this. If there's a way to do this in the design that would be great. If there's a way to do in the Initialize sub that would also be great.
Based on your intended usage, you should be using a TabStrip instead of MultiPage being most controls within are the same layout (same amount of controls). MultiPage is intended for categorizing data with different controls on each page.
Consider this simple userform to demostrate the benefit of using TabStrip here:
The left side square is a Picture holder I have not put codes for.
With below code to handle tab changes, certain elements in the userform will change when a different Tab is clicked.
Option Explicit
Private Sub TabStrip1_Change()
Dim TabX As String
With Me.TabStrip1
TabX = .Tabs(.Value).Caption
Debug.Print "ActiveTab:", TabX
End With
Me.Frame1.Caption = "Maximum Bounds (" & TabX & ")"
Me.Frame2.Caption = "Minimum Bounds (" & TabX & ")"
Me.TextBox1.Value = "TextBox2 for " & TabX ' Forgot to change this Value to TextBox1 before the screenshot...
Me.TextBox2.Value = "TextBox2 for " & TabX
End Sub
Upon launching the userform (without setting UserForm_Initialize):
Tab2 clicked:
Tab1 clicked:

Print keeps doing double sided (I only want one per page)

I have built the following VBA code:
Option Explicit
Private Sub PrintBtn_Click()
Worksheets.PrintOut from:=FromBox.Value, to:=ToBox.Value, Copies:=Copies.Value
End Sub
Private Sub CloseBtn_Click()
Unload Me
End Sub
Private Sub UserForm_Initialize()
Copies.Value = 1
FromBox.Value = 1
Dim i As Long
For i = 1 To ActiveWorkbook.Sheets.Count
SheetBox.Value = SheetBox.Value & i & " - " & ActiveWorkbook.Sheets(i).Name & vbCrLf
Next i
ToBox.Value = i - 1
End Sub
Basically populates a userform with the names of each worksheet in excel. There's a from box and a to box, and I'm trying to give the user an ability to print out each page individually. It works, the print function works, but rather than printing out each one on a separate page, after the first page it makes everything double sided!
Basically I want to print out sheets X through Y without having it be double sided. The usual print dialog box does not work and also makes it double sided. Perhaps it is a problem with the worksheets themselves, but I do not know what setting I can focus on or change to do so.

Adding Macro Code to Word Documents using VB.Net

Ok so let me explain in detail.
Suppose there is a word file called "Word.doc"
What I want to do is basically use VB.NET for doing the following things :
Open the word document
Add a macro code
For e.g
Add the following macro code to the Word Document
Sub AutoOpen()
Msgbox
End Sub
And then save this document.
Just remember that I want to insert macro code to a word document not retreive an already present macro code from a document
Here is a simple VBA macro that shows how to use the Word object model to add a macro to a document (VB.NET code will be almost identical).
Sub NewDocWithCode()
Dim doc As Document
Set doc = Application.Documents.Add
doc.VBProject.VBComponents("ThisDocument").CodeModule.AddFromString _
"Sub AutoOpen()" & vbLf & _
" MsgBox ""It works""" & vbLf & _
"End Sub"
End Sub
Note that running this code requires that access to the VBA project object model is trusted (this needs to be enabled in the trust center in the the Word options).
Working with objects in the VBA Editor through the object model requires a reference to the Microsoft Office VBA Extensibility 5.3 object model which you can find in the COM tab of Project/Add References in Visual Studio.
I like to add an Imports statement to the top of the code so that I don't have to always write out the full namespace qualification:
Imports VBE = Microsoft.Vbe.Interop
An AutoOpen macro needs to be a "public" Sub in a normal code module. Assuming you want to add a new code module to the document, use the VBComponents.Add method and specify the enumeration type vbext_ct_StdModule.
By default, the VBE will name the new module "Module#" - an incrementing number. If you ever need to programmatically address this module again (to see if it exists, for example) it would probably be better to assign a name to it.
Code as a string is added using the AddFromString method.
If you're adding code to a document the possibility exists that the document is of type docx, meaning it cannot contain macro code. A document must have the extension docm in order to contain macro code. So you may need to use the SaveAs method on the document to change the file type and the name!
Private Sub InsVbaCode_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InsVbaCode.Click
'Helper method to get current Word instance or, if none, start one
GetWordProcess()
Dim doc As Word.Document = WordApp.ActiveDocument
Dim vbModule As VBE.VBComponent = doc.VBProject.VBComponents.Add(VBE.vbext_ComponentType.vbext_ct_StdModule)
vbModule.Name = "basAddedCode"
vbModule.CodeModule.AddFromString( _
"Sub AutoOpen()" & vbLf & _
" MsgBox ""Document "" & ActiveDocument.FullName & "" has been opened successfully!""" & vbLf & _
"End Sub")
'doc.Save() or doc.SaveAs to change file type and/or name
End Sub