Reputation: 11
I need some help with Custom UI Editor code for a Ribbon in Word. The same code I've been using in Word 2010 does not work in Word 2019 any more. When I create a document based off a template the Ribbon is there and works. I save the document and close it. When I reopen the Ribbon is like "dead". It's not activated and the code no longer runs.
What has changed in Word 2019? What do I need to do to correct this issue?
Here is the Custom UI Code:
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<!--This is the Expense Template-->
<ribbon>
<tabs>
<tab id="customTab3" label="Expense">
<group id="customGroup110" label="Expense/Disbursement Calculate">
<button id="customButton110" size="large" label="Expense/Disbursement Calculate" imageMso="CreateReport" onAction="ExpenseCalculate" />
</group>
</tab>
</tabs>
</ribbon>
</customUI>
Here is the code that is in the template:
Sub ExpenseCalculate(control As IRibbonControl)
' Edited Code On: 2/6/2012
' Edited Code By: Sheila Shines
' Code Change: Added code to test to see what Template a user is in before running code
Dim rowz As Integer
Dim theRow As Integer
Dim rT As Integer
Dim myTemplate As Template
Set myTemplate = ActiveDocument.AttachedTemplate
If UCase(myTemplate.Name) = "EXPENSE.DOTM" Or UCase(myTemplate.Name) = "EXPENSE.DOT" Then
'MOVES TO BEGINNING OF DOCUMENT
Selection.HomeKey unit:=wdStory, Extend:=wdMove
'LINE DOWN
Selection.MoveDown unit:=wdLine, Count:=1, Extend:=wdMove
'LINE DOWN UNTIL INTO A TABLE
While Selection.Information(wdWithInTable) = 0
Selection.MoveDown unit:=wdLine, Count:=1, Extend:=wdMove
Wend
'MOVE TO START OF ROW OF THE TABLE
Selection.StartOf unit:=wdRow, Extend:=wdMove
'SELECTING TABLE
ActiveDocument.Tables(1).Select
'NUMBER OF ROWS IN THE TABLE
rowz = Selection.Information(wdMaximumNumberOfRows)
'MOVING LEFT ONE PLACE
Selection.MoveLeft unit:=wdCharacter, Count:=1, Extend:=wdMove
'ROW CONTAINING THE BEGINNING OF THE SELECTION
theRow = Selection.Information(wdStartOfRangeRowNumber)
'MOVING DOWN ONE LINE AT A TIME THROUGH TABLE
While theRow < rowz
Selection.MoveDown unit:=wdLine, Count:=1, Extend:=wdMove
theRow = Selection.Information(wdStartOfRangeRowNumber)
Wend
'MOVING OVER TWO CELLS
Selection.Move unit:=wdCell, Count:=2
'DELETING INFORMATION OUT OF CELL IF ANY
Selection.Range.Delete
rowz = rowz - 1
rT = Right$(Str$(rowz), Len(Str$(rowz)) - 1)
'INSERTING FIELD INTO TABLE
Selection.InsertFormula Formula:="=sum(c2:c" & CInt(rT) & ")", NumberFormat:="$#,##0.00;($#,##0.00)"
'PRINTING DOCUMENT
ActiveDocument.PrintOut
Else
MsgBox "You need to be in an Expense template in order to use this macro", vbCritical, "In wrong template"
End
End If
End Sub
Here is the solution that I have tried
What happens to the Word session ribbon after closing and reopening a document with a custom ribbon?
https://stackoverflow.com/questions/57841404/what-happens-to-the-word-session-ribbon-after-closing-and-reopening-a-document-w
Upvotes: 1
Views: 863
Reputation: 7850
The solution that you tried looks to be a real kludge and is definitely not something I would recommend. But as it partially worked for you it confirms the gut feeling that I had when I first read your question, that your ribbon needs to be invalidated (refreshed).
There is an answer here that gives the code you need, though I have a few issues with it.
At this point I have to explain that I do not put ribbons, or code, in document templates (I put all my code in a global template and then use ribbon callbacks to determine which controls should be visible/enabled) so I'm not sure if this will work for you.
To make this work you need to get a pointer to the ribbon when it loads. For that you need an onLoad callback and corresponding ribbon xml:
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="ribbonOnLoad">
Callback module (BTW, I took the liberty of rewriting your code):
Option Explicit
'CopyMemory for ribbon retrieval
#If Win64 Then
Declare PtrSafe Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
#Else
Public Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#End If
Private rbnUI As IRibbonUI
Public Property Get RibbonUI() As IRibbonUI
#If Win64 Then
Dim lngRbnPtr As LongPtr
#Else
Dim lngRbnPtr As Long
#End If
Dim objRibbon As Object
If rbnUI Is Nothing Then
'the pointer is lost so retrieve it from the registry
lngRbnPtr = GetSetting("Templates", "Ribbon", "Ribbon Pointer WD")
CopyMemory objRibbon, lngRbnPtr, 4
Set rbnUI = objRibbon
' clean up invalid object
CopyMemory objRibbon, 0&, 4
Set objRibbon = Nothing
End If
Set RibbonUI = rbnUI
End Property
Public Property Set RibbonUI(ribbon As IRibbonUI)
#If Win64 Then
Dim lngRbnPtr As LongPtr
#Else
Dim lngRbnPtr As Long
#End If
Set rbnUI = ribbon
lngRbnPtr = ObjPtr(ribbon)
'save pointer to registry for safe keeping
SaveSetting "Templates", "Ribbon", "Ribbon Pointer WD", lngRbnPtr
End Property
Private Sub ribbonOnLoad(ribbon As IRibbonUI)
' Store pointer to IRibbonUI
Set RibbonUI = ribbon
End Sub
Public Sub ribbonInvalidate()
On Error GoTo ProcError
RibbonUI.Invalidate
ProcExit:
'Clean up
On Error Resume Next
Exit Sub
ProcError:
If Err.Number = 91 Then
MsgBox "Unrecoverable Ribbon Error" & vbCrLf & "" & vbCrLf & _
"Unable to refresh the ribbon. Please save and reopen " & Application.name & _
".", vbOKOnly + vbExclamation, "Ribbon Error"
Else
'add your own
End If
End Sub
Sub ExpenseCalculate(control As IRibbonControl)
Dim myTemplate As Template
Set myTemplate = ActiveDocument.AttachedTemplate
If UCase(myTemplate.name) = "EXPENSE.DOTM" Or UCase(myTemplate.name) = "EXPENSE.DOT" Then
'INSERTING FIELD INTO TABLE
With ActiveDocument.Tables(1).Rows.Last.Cells(3)
.Range.Delete
.Formula Formula:="=sum(above)", NumFormat:="$#,##0.00;($#,##0.00)"
End With
'PRINTING DOCUMENT
ActiveDocument.PrintOut
Else
MsgBox "You need to be in an Expense template in order to use this macro", vbCritical, "In wrong template"
End
End If
Set myTemplate = Nothing
End Sub
Next you need a means of invalidating the ribbon. For this I use a class module that hooks into Word's application events. This enables me to invalidate the ribbon each time ActiveDocument
changes, i.e. creating or opening a document, switching between open documents.
Option Explicit
Public WithEvents appWord As Word.Application
Private Sub appWord_DocumentChange()
InvalidateRibbon
End Sub
The class module needs to be initialized when the template is loaded. This can be done using an AutoExec routine. I like to keep the actual initializing in a separate routine so that I can call it from a global error handling routine.
Public wordEvents As clsWordEvents
Public Sub AutoExec()
instantiateEventHandler
End Sub
Public Sub instantiateEventHandler()
If wordEvents Is Nothing Then
Set wordEvents = New clsWordEvents
Set wordEvents.appWord = Word.Application
End If
End Sub
Upvotes: 1
Reputation: 4913
By default, when you create a .dotx document from a template, the macro code is not copied to the document. Instead, the document remains linked to the template and calls the template to run the macro.
Your macro theoretically checks whether the document is attached to a particular template, but since the macro code is only in the template, it can't run if the document is attached to a different template. So nothing happens.
BTW, your heavy use of the Selection object indicates you created this with the macro recorder. That's a good initial learning step, but the code will be more reliable and run faster if you switch all statements to use the Range object instead.
Upvotes: 0