I have built a Word Macro-Enabled Template (.dotm) that works perfectly on my machine when I save it to my Word Startup folder. However, when I send the .dotm file to my colleagues (some on same environment as me and some not) an error is generated when pressing the custom ui ribbon button:
The macro cannot be found or has been disabled because of your security settings
The macro settings are set to run all macros, the folder containing the template >is a trusted location
.... more help options follow
My colleagues also save the file to their startup folder and load Word. The ribbon shows perfectly, but the macro does not run. My colleagues and I have done a boatload of searching online and cannot figure out the issue, as all my configurations are correct, as far as I can tell, which I will list below:
Macro Settings: Enable All Macros
Trust access to the VBA project object model is checked
Startup folder is established as a trusted location
Custom UI XML
<?xml version="1.0" encoding="utf-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<ribbon>
<tabs>
<tab id="customTab" label="WebMerge">
<group id="CustomHelp" label="Map Tags">
<button id="mapper"
visible="true"
size="large"
label="Open Mapper"
screentip="Opens Mapping Engine"
onAction="LoadMappingGuide"
imageMso="FindDialog"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>
UI Ribbon Connection in Standard Module
Option Explicit
Sub LoadMappingGuide(ByVal Control As IRibbonControl) 'also tried without ByVal
'select business unit or automatically detect based on username against our database ...
Dim mappingForm As New mappingGuide
mappingForm.Show vbModal
End Sub
mappingForm (UserForm) Code
Option Explicit
Private Sub cancelButton_Click()
Unload Me
End Sub
Private Sub searchBox_Change()
generateList
Dim n As Long, index As Long
index = 0
For n = 0 To smartTagList.ListCount - 1
If InStr(1, smartTagList.List(index, 0), searchBox.Value, vbTextCompare) > 0 Then
index = index + 1
ElseIf InStr(1, smartTagList.List(index, 1), searchBox.Value, vbTextCompare) > 0 Then
index = index + 1
ElseIf InStr(1, smartTagList.List(index, 2), searchBox.Value, vbTextCompare) > 0 Then
index = index + 1
Else
smartTagList.RemoveItem (index)
End If
Next n
End Sub
Private Sub smartTagList_Click()
If smartTagList.ListIndex > -1 And smartTagList.Selected(smartTagList.ListIndex) Then
Dim smartyTag As String
smartyTag = smartTagList.List(smartTagList.ListIndex, 2)
Selection.Range.Text = smartyTag
End If
Unload Me
End Sub
Private Sub UserForm_Initialize()
generateList
End Sub
Private Sub generateList()
'replace with code to get values from database
Dim fields() As String
Dim descriptions() As String
Dim smartyTags() As String
fields = Split("Producer Name,Producer Address,Producer City,Producer State,Producer Zip,Insured Name,Insured Address,Insured City,Insured State,Insured ZIp,Risk Premium,TRIA Premium,Final Premium", ",")
descriptions = Split("Name of Producer,Address Line of Producer,City of Producer,State of Producer,Zip Code of Producer,Name of Insured,Address of Insured,City of Insured,State of Insured,ZIp of Insured,Total Premium for all risks excluding terrorism taxes and surcharges.,Premium resulting from acceptance of terrorism protection,Total Premium of quote / policy", ",")
smartyTags = Split("{$Producer Name},{$ProducerAddress},{$ProducerCity},{$ProducerState},{$ProducerZip},{$InsuredName},{$InsuredAddress},{$InsuredCity},{$InsuredState},{$InsuredZIp},{$RiskPremium},{$TRIAPremium},{$FinalPremium}", ",")
Dim i As Long
For i = LBound(fields) To UBound(fields)
With smartTagList
.AddItem
.List(i, 0) = fields(i)
.List(i, 1) = descriptions(i)
.List(i, 2) = smartyTags(i)
End With
Next
End Sub
This is what my VBE looks like with .dotm open.
The most likely reason for the error message is that the macro is not available in the template containing the Ribbon customization. Often, they remain in the Normal.dotm template on the development machine.
Background:
Something the VBA developer always needs to keep in mind when starting out on a project and testing in Word is that Word works with multiple "contexts". First, foremost and the default when recording macros and creating functionality in the private user environment is the Normal.dotm template. This is "universal": it comes into play no matter in which document you work, even if that document is attached to a different template.
THe great danger in this is that you may forget to move code meant for a specific template from Normal.dotm to that template. Or, even if you do copy the code over, if code with the same procedure names is present in both templates, that in Normal.dotm may keep precedence.
This is especially a problem when assigning macros to Ribbon and QAT buttons. Even if Normal.dotm from the developer machine is no longer present, the Ribbon or QAT assignment still looks for that and doesn't check for an alternate source.
So if you decide you want to keep the code in Normal.dotm as well as in the specific template it's a good idea to rename the procedures in Normal.dotmto avoid conflicts.
Related
I have a custom ribbon, I'd like to make one of the buttons dynamic based on values in a table
so, when the form is closed, I'd like for the ribbon button and label to change based on changes made in the form (if there were any)
Here's my VBA code just for that button.
And I created 2 separate buttons with different images and labels in the USysRibbonImages table. I have mine setup a tad different, that table also stores button names, macro names and labels, besides the images. I did it that way since each image record can only be tied to one button anyway, so, figured I might as well make my images into buttons in the same table. And this process is automated somewhat. I'm explaining in case someone wonders why the code is a little different from what you usually see for ribbons.
But it works fine for static ribbons, it's once I want to make them a little dynamic is where I have issues
Public Sub fnGetRibbonImages(control As IRibbonControl, ByRef image)
Dim attach As Attachment
DoCmd.OpenForm "fZRibbonImages", acNormal, , , , acHidden
If control.ID = "btnServicesShippingWeightBucketsCollections" Or control.ID = "btnServicesShippingWeightBucketsCollectionsRed" Then
'regular
If dCount("MissingServicesShippingWeightBucketsID", "MissingServicesShippingWeightBuckets") > 0 Then
Forms("fZRibbonImages").Filter = "([USysRibbonImages].[ButtonName]='btnServicesShippingWeightBucketsCollections')"
Else
'red
Forms("fZRibbonImages").Filter = "([USysRibbonImages].[ButtonName]='btnServicesShippingWeightBucketsCollectionsRed')"
End If
Forms("fZRibbonImages").FilterOn = True
Set attach = Forms("fZRibbonImages").Controls.Item("Images")
Set image = attach.PictureDisp()
End If
DoCmd.Close acForm, "fZRibbonImages", acSaveYes
End Sub
Public Sub GetRibbonLabel(ByVal control As Office.IRibbonControl, ByRef returnedVal)
If control.ID = "btnServicesShippingWeightBucketsCollections" Or control.ID = "btnServicesShippingWeightBucketsCollectionsRed" Then
If dCount("MissingServicesShippingWeightBucketsID", "MissingServicesShippingWeightBuckets") > 0 Then
returnedVal = "FIX!!!!!"
Else
returnedVal = "Buckets"
End If
End If
End Sub
And here's my ribbon xml (I removed a bunch of buttons, the code is the same for all)
<?xml version="1.0" encoding="utf-8"?>
<customUI
xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="OnLoadRibbon" loadImage="OnLoadImage">
<ribbon startFromScratch="true">
<tabs>
<tab id="tabDefault" label="App Options">
<group id="grpMappings" label="Mappings">
<button id="btnZoneXWalk" label="Zone Xwalk" onAction="mZoneXWalk" getImage="fnGetRibbonImages" size="large"/>
<button id="btnServicesShippingWeightBucketsCollections" label="Shipping Weight Buckets" onAction="mServicesShippingWeightBucketsCollections" getImage="fnGetRibbonImages" size="large"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>
I tried putting this with the CLOSE button for the form
sbRefreshRibbon
MyRibbon.Invalidate
And here's the code for the sbRefreshRibbon sub
Public Sub sbRefreshRibbon()
On Error GoTo RestartApp
MyRibbon.Invalidate
On Error GoTo 0
Exit Sub
RestartApp:
MsgBox "Please restart Application for Ribbon changes to take effect", _
vbCritical, "Ribbon Refresh Failed"
End Sub
No luck, except after a few changes (don't remember exactly what I did, I've been at this for a while) I got an error that said to restart to reload the ribbon
Instead of the label attribute for the ribbon controls you need to use the getLabel one with a callback which should have the following signature:
C#: string GetLabel(IRibbonControl control)
VBA: Sub GetLabel(control As IRibbonControl, ByRef label)
C++: HRESULT GetLabel([in] IRibbonControl *pControl, [out, retval] BSTR *pbstrLabel)
Visual Basic: Function GetLabel(control As IRibbonControl) As String
So, in your ribbon XML file you need to replace the label attribute:
<group id="grpMappings" label="Mappings">
<button id="btnZoneXWalk" label="Zone Xwalk" onAction="mZoneXWalk" getImage="fnGetRibbonImages" size="large"/>
<button id="btnServicesShippingWeightBucketsCollections" getLabel="GetRibbonLabel" onAction="mServicesShippingWeightBucketsCollections" getImage="fnGetRibbonImages" size="large"/>
</group>
Read more about the Fluent UI (aka Ribbon UI) in the following articles:
Customizing the 2007 Office Fluent Ribbon for Developers (Part 1 of 3)
Customizing the 2007 Office Fluent Ribbon for Developers (Part 2 of 3)
Customizing the 2007 Office Fluent Ribbon for Developers (Part 3 of 3)
I'm trying to create a add in for MS Word with VBA.
It has a "AutoExec" procedure that creates a new item in the CommandBar("Text") collection (right click menu) and a "AutoExit" that deletes this created item.
As an example, I tried the code below that create an item "How Many Pages?", which executes a macro displaying the number of pages in the active document.
This is the AutoExec Code:
Public Sub AutoExec()
Dim objcommandbutton As CommandBarButton
Call MsgBox("AutoExec")
Set objcommandbutton = Application.CommandBars("Text").Controls.Add _
(Type:=msoControlButton, Before:=1)
objcommandbutton.Caption = "How Many Pages?"
objcommandbutton.OnAction = "HowManyPages"
End Sub
This is the AutoExit Code:
Public Sub AutoExit()
Dim objcommandbutton As CommandBarControl
Call MsgBox("AutoExit")
For Each objcommandbutton In Application.CommandBars("Text").Controls
If objcommandbutton.Caption = "How Many Pages?" Then
objcommandbutton.Delete
End If
Next objcommandbutton
End Sub
This is the Main Macro Code:
Public Sub HowManyPages()
If Documents.Count > 0 Then
Call MsgBox(ActiveDocument.BuiltInDocumentProperties("Number of Pages"))
Else
Call MsgBox("No document is currently active.")
End If
End Sub
When exiting the document, the Button previously added in CommandBars("Text") collection is not deleted. I see this when I open a new blank Word document and the button remains in the right click menu.
I know that the routine is performed correctly because there is a MsgBox instruction to verify it.
This only happen with the AutoExit subroutine of a add-in, that is, loaded as a add-in: running the code in a macro with vba module works fine.
Any help?
When working with the CommandBars object model in Word it's necessary to always specify the Application.CustomizationContext.
Word can save keyboard layouts and CommandBar customizations in various places: the Normal.dotm template, the current template or the current document. The default when you create a CommandBar addition may not be the3 same as the default when trying to delete something.
Since this is an add-in, I assume you want the change for the entire Word environment (any open document). In that case, use the context NormalTemplate. Use this before any calls to CommandBar:
Application.CustomizationContext = NormalTemplate
Note: for saving a customization in the current document: = ActiveDocument; for saving in the template attached to the current document: = ActiveDocument.AttachedTemplate.
I solved my problem with a workaround:
I was trying to "add" a template (.dotm) as a add-in (in the "Templates and Add-ins" window) to use my VBA project in a new document. That's why I was using the AutoExec() and AutoExit() procedures.
But only now I figure out that just "attaching" the .dotm template to the active document (in the same "Templates and Add-ins" window, as show in the figure below) makes the functions Private Sub Document_Open() and Private Sub Document_Close() to run normally. Which solves my problem.
Even so, I think there is a certain "issue" with the AutoExit() procedure when trying to change the CommandBars itens. But that's ok for now.
enter image description here
I want to add a handwritten digital signature to some of my company's forms.
The goal is to, select a document, add a signature (through the use of a drawing pad, which can be done with Excel's Ink Tools) and store the file in the server as PDF. This would cut out the necessity of printing and then scanning the form back in to obtain a signature.
I'm using Excel for the main interface for file manipulation and search. I've not found any references/libraries for the use of Excel - Ink Tools through VBA.
How do I start Ink Tools Objects in VBA? Would I have to use a different software to get the signature?
Update:
After #Macro Man pointed me in the right direction I found some material that helped get the eSignature up and running.
I've found some material on MSDN Digital Ink Signatures - Concepts and Technologies and InkPicture Class that talk about the Ink collection on VB.net and C# through a PictureBox/Userform , this coupled with the InkEdit Control in another Stackoverflow response in which I realised that VBAs tool box had a InkPicture Control additional control that could be utilized to collect the handwritten eSignature through User form.
Please find below step by step:
In VBAs toolbox on additional control under Tools > Additional Controls there is the InkPicture Control which allows you to create a signature Userform.
Once Added InkPicture can be used as any other control on the toolbox.
Then its a case of initialising the UserForm for the Signature request. I'm using a drawing pad, but other hardware should work as well.
And storing or utilising the resultant image at need, in my case saving a temp version in the server to then resize and add to a Word document.
Edit:
After answering a similar question in here, on how to use Userform InkPicture to input image signature into a worksheet/specific cell, I thought I'd edit this answer for those interested.
The below code will allow you to, open the userform so the user can sign the ink field, save the image temperately, add the InkPicture to your worksheet and kill the temp image.
Set up your UserForm (mine is set up like this, with a couple extra options) the UserForm is named Signature_pad, the essential option you need is Private Sub Use_Click().
This is the code inside the Userform:
Private Sub Use_Click()
'dim object type and byte array to save from binary
Dim objInk As MSINKAUTLib.InkPicture
Dim bytArr() As Byte
Dim File1 As String
'get temp file path as $user\Temp\[file name]
FilePath = Environ$("temp") & "\" & "Signature.png"
' set objInk as image/strokes of InkPicture control form object
Set objInk = Me.SignPicture
'if object is not empty
If objInk.Ink.Strokes.Count > 0 Then
'get bytes from object save
bytArr = objInk.Ink.Save(2)
'create file for output
Open FilePath For Binary As #1
'output/write bytArr into #1/created (empty)file
Put #1, , bytArr
Close #1
End If
'set public File as file path to be used later on main sub
Signature.File = FilePath
Unload Me
End Sub
Private Sub Cancel_Click()
End
End Sub
Private Sub ClearPad_Click()
'delete strokes/lines of signature
Me.SignPicture.Ink.DeleteStrokes
'refresh form
Me.Repaint
End Sub
Below is the Main sub (Module called Signature) to call the userform and handle the signature, you can call this Sub with a button or form another Sub.
'public temp file path
Public File
Sub collect_signature()
'Dim and call userform
Dim myUserForm As Signature_pad
Set myUserForm = New Signature_pad
myUserForm.Show
Set myUserForm = Nothing
'insert image/signature from temp file into application active sheet
Set SignatureImage = Application.ActiveSheet.Shapes.AddPicture(File, False, True, 1, 1, 1, 1)
'scale image/signature
SignatureImage.ScaleHeight 1, True
SignatureImage.ScaleWidth 1, True
'image/signature position
SignatureImage.Top = Range("A1").Top
SignatureImage.Left = Range("A1").Left
'delete temp file
Kill File
End Sub
Be sure to rename either the Userform Name and Buttons Name Or the code to match the names of you buttons.
Is it the InkEdit Control you're after?
This is one of the standard libraries that you can find in Tools->References
So I have a work around to my problem, but I don't really understand the problem, and my work-around is crude. I have a document level customization that can insert sheets from other documents not included in the customized document:
Private Sub LabReportTemplateAdder()
Dim ReportTemplate As Excel.Workbook
CurrentRun = Marshal.GetActiveObject("Excel.Application")
ReportTemplate = CurrentRun.Workbooks.Open("C:\Reports\Templates\" & LabReportListBox.SelectedItem())
ReportTemplate.Worksheets(1).Move(Before:=Globals.ThisWorkbook.Sheets(5))
End Sub
This script actually works fine every time in the deployment. But when I try to modify the added template (ie add information from a database) the modifications (many different actions) all fail with a missing reference error:
"This document might not function as expected because the following control is missing: Sheet5. Data that relies on this control will not be automatically displayed or updated, and other custom functionality will not be available. Contact your administrator or the author of this document for further assistance."
An examples of the type of code that fails:
Private Sub AllMaterialsAdder(xxDataGridView As DataGridView, CostColumnID As Double, InsertColumnID As Double, CountColumnID As Double, DescriptionIndex As Integer, CostIndex As Integer)
CurrentSheet = Globals.ThisWorkbook.ActiveSheet
If CurrentSheet.Name = NameSet Then 'this is abbreviated test to check make sure only the sheets we need are added
MsgBox("The active sheet isn't a Lab Report. It's " & CurrentSheet.Name & ".")
Else
Dim ItemCount As Double
ItemCount = CurrentSheet.Cells(1, CountColumnID).value
For Each row As DataGridViewRow In xxDataGridView.SelectedRows
CurrentSheet.Cells((4 + ItemCount), InsertColumnID).value = xxDataGridView.Item(DescriptionIndex, row.Index).Value
CurrentSheet.Cells((4 + ItemCount), CostColumnID).value = xxDataGridView.Item(CostIndex, row.Index).Value
ItemCount = ItemCount + 1
Next
End If
End Sub
or
Private Sub MaterialSummaryUpdater()
CurrentSheet = Nothing
Globals.MaterialSummaryWorksheet.UsedRange(5, 26).Clear()
For Each Me.CurrentSheet In Globals.EOSWorkbook.Worksheets
If CurrentSheet.Name <> NameSet Then 'this is abbreviated test to check make sure only the sheets we need are added [excluding NameSet]
Dim CurrentCount1, CurrentCount2, CurrentCount3, MasterCount1, MasterCount2, MasterCount3 As Int32
CurrentCount1 = CurrentSheet.Cells(1, 28).Value
CurrentCount2 = CurrentSheet.Cells(1, 33).Value
CurrentCount3 = CurrentSheet.Cells(1, 39).Value
If CurrentCount1 > 0 Then
MasterCount1 = Globals.MaterialSummaryWorksheet.Cells(2, 3).Value
Globals.MaterialSummaryWorksheet.Range(Globals.MaterialSummaryWorksheet.Cells((5 + MasterCount1), 1), Globals.MaterialSummaryWorksheet.Cells((4 + MasterCount1 + CurrentCount1), 6)).Value = CurrentSheet.Range(CurrentSheet.Cells(4, 25), CurrentSheet.Cells((3 + CurrentCount1), 30)).Value
End If
If CurrentCount2 > 0 Then
MasterCount2 = Globals.MaterialSummaryWorksheet.Cells(2, 8).Value
Globals.MaterialSummaryWorksheet.Range(Globals.MaterialSummaryWorksheet.Cells((5 + MasterCount2), 7), Globals.MaterialSummaryWorksheet.Cells((4 + MasterCount2 + CurrentCount2), 10)).Value = CurrentSheet.Range(CurrentSheet.Cells(4, 31), CurrentSheet.Cells((3 + CurrentCount2), 35)).Value
End If
If CurrentCount3 > 0 Then
MasterCount3 = Globals.MaterialSummaryWorksheet.Cells(2, 13).Value
Globals.MaterialSummaryWorksheet.Range(Globals.MaterialSummaryWorksheet.Cells((5 + MasterCount3), 12), Globals.MaterialSummaryWorksheet.Cells((4 + MasterCount3 + CurrentCount3), 16)).Value = CurrentSheet.Range(CurrentSheet.Cells(4, 36), CurrentSheet.Cells((3 + CurrentCount3), 40)).Value
End If
End If
Next
End Sub
I should note that this doesn't happen on the computer I'm developing on, but only in deployment, which might make it related to this question or this one.
Though I feel like the problem is bit different for me. First, all of the machines I am trying to deploy this on do have VSTO Tools for Office installed. Secondly, if I run a script that calls one of the Named Sheets within the customized document, it works. Simply adding a worthless variable before I ever add in a sheet seems to fix the problem:
Dim currentcount As Int32 = Globals.HistologyLaborSummaryWorksheet.Cells(2, 11).value
But if I call that after a sheet has been added it doesn't matter it will fail. My simple work around was to add this to the LabReportTemplateAdder sub, but I still don't understand why it should fail, and why that would fix it. Clearly the sheets exist, but I don't know if it has something to do with modifying the sheet index or if the worksheets need to be registered somewhere similar to the ROT problem I ran into earlier.
I'm looking for a better solution if there is one, and an explanation as to what is really failing here.
Thanks.
EDIT:
I'm running into this now in more places, and again, the work around seems crude. Here is a full error:
Microsoft.VisualStudio.Tools.Applications.Runtime.ControlNotFoundException:
This document might not function as expected because the following control is missing:
Sheet5. Data that relies on this control will not be automatically displayed or updated,
and other custom functionality will not be available. Contact your administrator or the
author of this document for further assistance. --->
System.Runtime.InteropServices.COMException: Programmatic access to the Microsoft Office
Visual Basic for Applications project system could not be enabled. If Microsoft Office
Word or Microsoft Office Excel is running, it can prevent programmatic access from being
enabled. Exit Word or Excel before opening or creating your project.
at Microsoft.VisualStudio.Tools.Office.Runtime.Interop.IHostItemProvider.GetHostObject(String primaryType, String primaryCookie, IntPtr& hostObject)
at Microsoft.VisualStudio.Tools.Office.Runtime.DomainCreator.ExecuteCustomization.Microsoft.Office.Tools.IHostItemProvider.GetHostObject(Type primaryType, String primaryCookie)
at Microsoft.Office.Tools.Excel.WorksheetImpl.GetObjects()
--- End of inner exception stack trace ---
at Microsoft.Office.Tools.Excel.WorksheetImpl.GetObjects()
at Microsoft.Office.Tools.Excel.WorksheetImpl.GetPrimaryControl()
at Microsoft.Office.Tools.Excel.WorksheetImpl.get_Cells()
at Microsoft.Office.Tools.Excel.WorksheetBase.get_Cells()
This looks relevant based on the fact that you are inserting sheets.
I've inherited a Access database that is used to import some data in SQL. The MDB opens with a form, in a modal mode: no Access menubar or buttons are visible. I can view the tables with content by using the Visual Studio 'Data Connection' tool, but I cannot see the module's code.
I've looked at this question here, but the answers there aren't really what I need. Is there a way to either force the form to close (and access the modules) or to extract the VBA code ?
[EDIT] I am using Access 2007, not sure what the original developer used.
Hold down the shift key when you open the database. This will prevent it from loading the automatic script it's running and allow you to gain access to the tables, queries, and VBA scripts.
This is a rather long addendum to, or comment on, Michael Todd's reply in response to edosoft's comment.
Rather than choosing to enable the content, check the start-up options (either (file->options->current database->display form) or (tools->start up->display form)) and remove the name of the form, having taken a note, and ensure that Allow Full Menus (same page) is ticked. You may also like to press Alt+F11 to display code, and check that for start-up code, finally, see if there is an AutoRun macro and rename it.
EDIT re Comments
You do not have to open an mdb to change the start-up form, for example, code such as this could be run from another mdb using the full name and path of the mdb you wish to change.
Sub SetStartForm(DBFile As String)
Dim prp As Object
Dim db As Database
Const PROPERTY_NOT_FOUND As Integer = 3270
Set db = OpenDatabase(DBFile)
db.Properties("StartupForm") = "(none)"
If Err.Number > 0 Then
If Err.Number = PROPERTY_NOT_FOUND Then
'' Create the new property, but this is not relevant in this case
End If
End If
db.Close
Set db = Nothing
Set prp = Nothing
End Sub
Button close with function:
Private sub cmdClose_Click()
CloseForm(YourFormName)
End sub
Public Sub CloseForm(ByVal strFormName As String)
Dim iCounter As Integer
For Each frm In Forms
With frm
If (.Name = strFormName) Then
iCounter = iCounter + 1
End If
End With
Next
If (iCounter > 0) Then
For i = 1 To iCounter
DoCmd.Close acForm, strFormName, acSaveNo
Next
End If
End Sub