Custom UI Editor Ribbon not loading after closing document and reopening - vba

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

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

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.

Related

Make Individual Serial for Print documents in Word

Dear Readers
I am trying to make individual Serial numbers ( incrementing number) for some Forms in Microsoft Word, so we can track each one of them much simpler between people.
I used this link and it did work,
but it needs always running Macro and I couldn't figure out how to make it automatic with just a simple Ctrl+P shortcut, so I used this second link for that reason,
finally, it looked so great but there is a problem since the second link is a just "before Print" code, there is always one extra print at the end since the Printing process starts exactly after macro ended. any cancel print process code out there?
how can I overcome this one?
Codes under the document
Private Sub Document_Open()
Register_Event_Handler
End Sub
Codes under the Module
Dim X As New EventClassModule
Sub Register_Event_Handler()
Set X.App = Word.Application
End Sub
Codes under the class
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
' Run code directly inside this Sub OR
MsgBox "Before Print"
' Call another Sub here, note, Sub and Module name can't match
Call FilePrint
' See https://www.freesoftwareservers.com/wiki/compile-error-expected-variable-or-procedure-not-module-macros-microsoft-office-29982732.html
End Sub
and finally Codes of FilePrint section as a Module
Sub FilePrint()
Dim i As Long, j As Long
With ActiveDocument
j = CLng(InputBox("How many copies to print?", "Print Copies"))
For i = 1 To j
With .CustomDocumentProperties("Counter")
.Value = .Value + 1
End With
.Fields.Update
ActiveDocument.PrintOut Copies:=1
Next
.Save
End With
End Sub

Refresh built-in Ribbon button after Options.DefaultHighlightColorIndex change and avoid exiting "Text Highlight Color"

What I'm trying to get working:
activate the Text Highlight Color command via a keybinding (not the problem)
cycle through 5 of the Default Text Highlight Colors via the same keybinding (or just highlighting the selection, depending on selection.type checked outside the function below)
showing the current Color in the corresponding button (built-in ribbon)
Where I'm stuck:
Sub cycleThroughSomeDefaultHighlightColorIndexOptions()
Dim zeNewColor As Long
Select Case Options.DefaultHighlightColorIndex
Case wdYellow: zeNewColor = wdBrightGreen
Case wdBrightGreen: zeNewColor = wdTurquoise
Case wdTurquoise: zeNewColor = wdPink
Case wdBlue: zeNewColor = wdRed
Case wdRed: zeNewColor = wdYellow
Case Else: zeNewColor = wdYellow
End Select
Application.Options.DefaultHighlightColorIndex = zeNewColor
End Sub
doesn't throw any error, does change the Application.Options.DefaultHighlightColorIndex,
but doesn't update/show the newly set color on the corresponding (built-in ribbon home tab) button
and just exits out of the Text Highlight Color mode.
Is there a possibility to keep it going?
If it needs to be started again: is there a better way than
dirty/interfering sendKeys to call commands like Text Highlight
Color?
Update 2019-04-03:
In the mean time i found where the IRibbonUI.InvalidateControlMso ControlIDs are listed: Office 2016 Help Files: Office Fluent User Interface Control Identifiers
So after creating a hidden custom ribbon and getting a handle for it on onLoad i could zeWdRibbon.InvalidateControlMso "TextHighlightColorPicker" without any raised error.
But it also doesn't change anything.
Is it possible, that Microsoft just getImages the default imageMso "TextHighlightColorPicker" (yellow) without checking for Application.Options.DefaultHighlightColorIndex , or am I missing something?
I do something like that, each time gRibbon.Invalidate
#If VBA7 Then
Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
#Else
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
#End If
Public gRibbon As IRibbonUI
#If VBA7 Then
Function GetRibbon(ByVal lRibbonPointer As LongPtr) As Object
#Else
Function GetRibbon(ByVal lRibbonPointer As Long) As Object
#End If
Dim objRibbon As Object
Call CopyMemory(objRibbon, lRibbonPointer, LenB(lRibbonPointer))
Set GetRibbon = objRibbon
Set objRibbon = Nothing
End Function
Public Sub OnRibbonLoad(ribbon As IRibbonUI)
Set gRibbon = ribbon
'SAVE SETTINGS TO REGISTRY
SaveSetting "POP", "RIBBON", "ribbonPointer", ObjPtr(gRibbon)
End Sub
Public Sub OnActionButton(control As IRibbonControl)
If gRibbon Is Nothing Then
Set gRibbon = GetRibbon(GetSetting("POP", "RIBBON", "ribbonPointer"))
End If
On Error Resume Next
gRibbon.Invalidate
On Error GoTo 0
End Sub

Programmatically dismiss a MsgBox

I have a master macro in an Excel file, 'file A' that opens another Excel file, 'file B'. On open, an add-in imports data into 'file B'. I would like to close 'file B' once the add-in is finished importing, and I'm looking for the best way to do that.
I've written the code to open 'file B' (which triggers the add-in automatically) and to close the file, but when the add-in is finished, it opens a MsgBox to notify the user. I'm trying to completely automate an internal process, so dismissing the MsgBox programmatically would be ideal.
Is it possible to dismiss a MsgBox through VBA? I'm aware that I can create timed MsgBoxes in VBA but I'm not creating this MsgBox (the add-in is); I just want to dismiss it. I'm open to creating a Word file and calling a macro from that if required, but would prefer not to use SendKeys.
Since the "add-in" and Excel/VBA run in the same context, we cannot launch it and monitor its message-box within the same VBA application, because each VBA application is a single-threaded process. Fortunately however, there is a solution that can exploit the fact that different VBA applications run in different contexts, so they can run in parallel.
My suggested solution is to create a MS-Word document that is dedicated to monitoring and closing that message box. We need this in Word (or any other office application) in order to make the monitoring code and the addin's code run in parallel, in different contexts.
1- create a Word macro-enable document, named mboxKiller.docm and place it in some folder; i.e. C:\SO in my example. place this code in ThisDocument and save:
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Sub WaitAndKillWindow()
On Error Resume Next
Dim h As Long: h = FindWindow(vbNullString, "Microsoft Excel")
If h <> 0 Then SendMessage h, 16, 0, 0 ' <-- WM_Close
Application.OnTime Now + TimeSerial(0, 0, 1), "WaitAndKillWindow"
End Sub
Private Sub Document_Open()
WaitAndKillWindow
End Sub
2- In the Excel workbook's VBA, create a class module, named mboxKiller with this code:
Private killerDoc As Object
Private Sub Class_Initialize()
On Error Resume Next
Set killerDoc = CreateObject("Word.Application").Documents.Open(Filename:="C:\SO\mboxKiller.docm", ReadOnly:=True)
If Err.Number <> 0 Then
If Not killerDoc Is Nothing Then killerDoc.Close False
Set killerDoc = Nothing
MsgBox "could not lauch The mboxKiller killer. The message-box shall be closed manuallt by the user."
End If
End Sub
Private Sub Class_Terminate()
On Error Resume Next
If Not killerDoc Is Nothing Then killerDoc.Application.Quit False
End Sub
3- Testing and Usage. In a normal class Module, place the following code and test the procedure
Sub Test() ' <-- run this for testing after finishing the setup
Dim killer: Set killer = New mboxKiller
simulateAddin
simulateAddin
simulateAddin
End Sub
' Procedure supposed to do some calculation then display a message box
Private Sub simulateAddin()
Dim i As Long
For i = 0 To 1000: DoEvents: Next ' simulates some calculations
MsgBox "This is a message box to simulate the message box of the addin." & VbCrLf & _
"It will be automatically closed by the Word app mboxKiller"
End Sub
VBA also has the ability to temporarily dismiss alerts.
Application.DisplayAlerts = False
'while you run your code here, no alerts will be displayed
Application.DisplayAlerts = True

How to set a text on a Editbox Ribbon via VBA (Excel)

How can I set a text in a Ribbon Editbox? I can't find it on internet :/
I just can find examples of click event but nothing about set a text from a Sub.
So for example, I want something like this:
Sub settingText()
editboxname = "my text"
end sub
The solution I found on this link: http://www.shulerent.com/2011/08/16/changing-the-value-of-an-editbox-office-ribbon-control-at-runtime/
Here is an example that I tested and it worked well:
'Global Variables:
Public MyRibbonUI As IRibbonUI
Public GBLtxtCurrentDate As String
Private Sub OnRibbonLoad(ribbonUI As IRibbonUI)
Set MyRibbonUI = ribbonUI
GBLtxtCurrentDate = ""
End Sub
Private Sub ocCurrentDate(control As IRibbonControl, ByRef text)
GBLtxtCurrentDate = text
MyRibbonUI.InvalidateControl (control.id)
End Sub
Private Sub onGetEbCurrentDate(control As IRibbonControl, ByRef text)
text = GBLtxtCurrentDate
End Sub
Public Sub MyTest()
'Here is an example which you are setting a text to the editbox
'When you call InvalidateControl it is going to refresh the editbox, when it happen the onGetEbCurrentDate (which is the Gettext) will be called and the text will be atributed.
GBLtxtCurrentDate = "09/09/2013"
MyRibbonUI.InvalidateControl ("ebCurrentDate")
End Sub
<?xml version="1.0" encoding="UTF-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="OnRibbonLoad">
<ribbon>
<tabs>
<tab id="Objects" label="Objects">
<group id="grp" label="My Group">
<editBox id="ebCurrentDate" label="Date" onChange="ocCurrentDate" getText="onGetEbCurrentDate"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>
It's a little while since this answer was posted, and there looks to be a recent-ish change to the behaviour of the ribbon, which means the original answer posted may not be a solution any more. For the record, I'm using Excel 2013 with some updates that are dated after Braulio's answer.
The heart of the difference is that Invalidate and InvalidateControl on the ribbon don't behave the same way as previously. This means that InvalidateControl does not call the getText callback on the editBox. I replaced the InvalidateControl calls with Invalidate (so forces a re-draw on the entire ribbon), and that does trigger the callback as expected.
So here's the code of my solution for a filename/browse button (note I've included extra code for caching the ribbon UI reference on a very hidden sheet so that resets during development don't make the ribbon inaccessible).
Private sobjRibbon As IRibbonUI
Private strFilename As String
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (destination As Any, source As Any, ByVal length As Long)
Private Function GetRibbon() As IRibbonUI
If sobjRibbon Is Nothing Then
Dim objRibbon As Object
CopyMemory objRibbon, ThisWorkbook.Worksheets("Ribbon_HACK").Range("A1").Value, 4
Set sobjRibbon = objRibbon
End If
Set GetRibbon = sobjRibbon
End Function
'Callback for customUI.onLoad
Sub Ribbon_Load(ribbon As IRibbonUI)
Set sobjRibbon = ribbon
Dim lngRibPtr As Long
lngRibPtr = ObjPtr(ribbon)
' Write pointer to worksheet for safe keeping
ThisWorkbook.Worksheets("Ribbon_HACK").Range("A1").Value = lngRibPtr
strFilename = ""
End Sub
'Callback for FileName onChange
Sub OnChangeFilename(control As IRibbonControl, text As String)
strFilename = text
End Sub
'Callback for FileName getText
Sub GetFileNameText(control As IRibbonControl, ByRef returnedVal)
returnedVal = strFilename
End Sub
'Callback for FilenameBrowse onAction (I'm looking for XML files here)
Sub OnClickFilenameBrowse(control As IRibbonControl)
Dim objFileDialog As Office.FileDialog
Set objFileDialog = Application.FileDialog(msoFileDialogFilePicker)
With objFileDialog
.AllowMultiSelect = False
.Title = "Please select the file."
.Filters.Clear
.Filters.Add "XML", "*.xml"
If .Show = True Then
strFilename = .SelectedItems(1)
GetRibbon().Invalidate ' Note the change here, invalidating the entire ribbon not just the individual control
End If
End With
End Sub
For the record, here's the XML for the two objects I'm dealing with here:
<editBox id="FileName" onChange="OnChangeFilename" screentip="Filename of the XML file to upload" label="XML file name" showImage="false" getText="GetFileNameText" />
<button id="FilenameBrowse" imageMso="ImportExcel" onAction="OnClickFilenameBrowse" screentip="Find the file to upload" label="Browse" />

How to disable the MS Word "macro-free document" warning with code?

I have an application that dynamically insert VBA code to help building a MS word document with proper bookmarks. The VBA code doesn't need to be saved with the document itself.
When saving the document, the following warning (unfortunately I can't post an image yet) will pop up that confuses the end users of the application. Is there a way to disable this within the DocumentBeforeSave event?
**
The following cannot be saved in a macro-free document: VBA project To
save a file with these features, click No to return to the Save As
dialog, and then choose a macro-enabled file type in the File Type
drop-down. Continue saving as a macro-free document?
buttons: [Yes][No][Help]
**
One idea is to change the document's SaveFormat to an older format in order to prevent this warning from popping up. But I'm not sure if this change will affect how the document behaves going forward and if it's even possible to modify this property within the DocumentBeforeSave event (the property is a READONLY property).
Thanks in advance for any help on this topic.
The following code will open a word (assuming c:\work\test.docx exist, which can be just a blank word doc). If you hit the Save button on the word doc, the warning message will show up. BTW, I'm using Office 2010.
<TestMethod()>
Public Sub testWord()
Dim wApp As New Word.Application()
Dim myDoc As Word.Document
Dim DataCodeModule As Object = Nothing
myDoc = wApp.Documents.Open("C:\Work\test.docx")
DataCodeModule = myDoc.VBProject.VBComponents(0).CodeModule
With DataCodeModule
.InsertLines(1, "Option Explicit")
.InsertLines(2, "Sub TestCode()")
.InsertLines(3, "Selection.InsertAfter ""test""")
.InsertLines(4, "End Sub")
End With
wApp.Visible = True
myDoc.Activate()
End Sub
And once the DocumentBeforeSave is hooked up, I was hoping the following code would disable the warning. Maybe the DisplayAlerts need to be set as soon as the document is opened?
Public Sub App_DocumentBeforeSave(ByVal doc As Object, ByRef saveAsUI As Boolean, ByRef cancel As Boolean) Handles _officeHelper.DocumentBeforeSave
Dim WordApp As Object = this.WordApp()
'WordApp.DisplayAlerts = False
WordApp.DisplayAlerts = 0
End Sub
Like this?
Application.DisplayAlerts = wdAlertsNone
'~~> Your Save Code
Application.DisplayAlerts = wdAlertsAll
FOLLOWUP
You are doing it in vb.net. I don't have access to VB.net at the moment but this example below will set you on the right path
Open Word and insert a module and then paste this code
Option Explicit
Dim MyClass As New Class1
Sub Sample()
Set MyClass.App = Word.Application
End Sub
Now insert a Class Module and paste this code
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforeSave(ByVal Doc As Document, _
SaveAsUI As Boolean, Cancel As Boolean)
Application.DisplayAlerts = wdAlertsNone
ActiveDocument.Save
Application.DisplayAlerts = wdAlertsAll
End Sub
Now if you press the save button, you will notice that you won't get that alert any more. :)
Hope you can adapt it as required :)
I simply done this code . It will prompt you to save and also help you in attaching it to email.
It worked for me.
Please try. I gave a command button on top of the sheet.
Thanks
Private Sub CommandButton1_Click()
Dim whatfolder, whatfile As String
whatfolder = InputBox("Type folder name.. Don't type which drive")
whatfile = InputBox("Type file name you want to give")
ChangeFileOpenDirectory "C:\" & whatfolder
Application.DisplayAlerts = wdAlertsNone
ActiveDocument.SaveAs FileName:=whatfile & ".docx", FileFormat:=wdFormatXMLDocument, LockComments:=False, Password:="", AddToRecentFiles:=True, WritePassword:="", ReadOnlyRecommended:=False, EmbedTrueTypeFonts:=False, SaveNativePictureFormat:=False, SaveFormsData:=False, SaveAsAOCELetter:=False
Application.DisplayAlerts = wdAlertsNone
ActiveDocument.SendMail
End Sub